@cachly-dev/mcp-server 0.10.208 → 0.10.209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -288,11 +288,10 @@ known cause. Ready-to-paste templates live in [`src/ci-integration/`](./src/ci-i
288
288
 
289
289
  - **GitHub Actions:** use [cachly-dev/cachly-action](https://github.com/cachly-dev/cachly-action).
290
290
  It needs the secrets `CACHLY_API_KEY` and `CACHLY_BRAIN_INSTANCE_ID`.
291
- - **GitLab CI:** copy [`brain-from-ci-gitlab.yml`](./src/ci-integration/brain-from-ci-gitlab.yml)
292
- into your pipeline. It adds two `.post` jobs (`on_success` and `on_failure`) with
293
- `allow_failure: true`. The
294
- [full GitLab template](https://github.com/cachly-dev/cachly-action/blob/main/templates/cachly.gitlab-ci.yml)
295
- adds hidden jobs for `learn`, `scan` and `confirm`. Pull it in with `include: remote:`.
291
+ - **GitLab CI:** pull in the
292
+ [GitLab template](https://github.com/cachly-dev/cachly-action/blob/main/templates/cachly.gitlab-ci.yml)
293
+ with `include: remote:`. It adds hidden jobs for `learn`, `scan`, `confirm` and `hygiene`;
294
+ extend the ones you want.
296
295
  - **Anything else:** [`push-ci-outcome.mjs`](./src/ci-integration/push-ci-outcome.mjs) is a
297
296
  standalone Node.js helper with no dependencies. It always exits 0, so your CI never
298
297
  fails because of a Brain push.
package/dist/src/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import{setzeClientKennung as Pt}from"./herkunft.js";import{jwtExpiryMs as Ht,checkJwt as Mt,handleApiError as Dt,diagnoseAuth as Se,planAuthHeal as Ue,readClientCredentialsFromEnv as Ut,buildClientCredentialsBody as zt,clientCredentialsTokenUrl as Jt}from"./auth.js";import{cachlyUrl as R}from"./cachly-url.js";import{nutzungsSchluessel as Ft}from"./werkzeug-nutzung.js";import{istLieferung as Wt,lieferSchluessel as Yt,tokenDerAntwort as Kt,hatTreffer as qt,trefferSchluessel as ze}from"./geliefert.js";import{notify as Je}from"./notifier.js";import{fileURLToPath as Fe}from"node:url";import{createRequire as Gt}from"node:module";import{execFile as Ae}from"node:child_process";const Vt=process.argv[1]!=null&&(process.argv[1]===Fe(import.meta.url)||process.argv[1].endsWith("/dist/index.js")||process.argv[1].endsWith("/src/index.ts"));import{Server as We}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Zt}from"@modelcontextprotocol/sdk/server/stdio.js";import{StreamableHTTPServerTransport as Qt}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{CallToolRequestSchema as Ye,ErrorCode as ie,ListPromptsRequestSchema as Ke,ListResourceTemplatesRequestSchema as qe,ListResourcesRequestSchema as Ge,ListToolsRequestSchema as Ve,McpError as oe}from"@modelcontextprotocol/sdk/types.js";import{Redis as Ze}from"ioredis";let x=process.env.CACHLY_API_URL??"https://api.cachly.dev",y=Be()??"";le(y);const Os=process.env.CACHLY_EMBED_MODEL??"",D=(()=>{try{const e=Gt(import.meta.url);for(const t of["../../package.json","../package.json"])try{const s=e(t);if(s.name==="@cachly-dev/mcp-server"&&s.version)return s.version}catch{}}catch{}return"0.10.100"})(),Xt=Number(process.env.CACHLY_PROVISION_TIMEOUT_MS??9e4);let N=xe(process.env.CACHLY_BRAIN_INSTANCE_ID);function ne(){return y||Be()||""}function F(){return xe(process.env.CACHLY_BRAIN_INSTANCE_ID)||pt()||""}let Q=0,de=null,Qe=!1;async function eo(){let e=[];try{V.getClientCapabilities()?.roots&&(e=(await V.listRoots(void 0,{timeout:3e3})).roots)}catch{}return jn({wurzeln:e,env:process.env,cwd:process.cwd(),dateiPfad:Fe})}function re(){return Qe?ft(process.env):!1}function Ce(e,t,s){!e||!re()||Tn({instanzId:e,vomHook:s,verbindung:H,ausGit:n=>je("brain_from_git",n,H,P),projektOrdner:t?async()=>t:eo,einbetten:n=>tt(n,{geduld:"lang"}),hatEinbettung:go,einbettenZweit:n=>tt(n,{geduld:"lang",modell:fo}),melde:n=>S("brain_from_git",{...y?{api_key:y}:{},instance_id:e,total:n,auto:"startwissen"}),startvorrat:n=>Oe("brain_seed_starter",n,H,P),meldeStartvorrat:n=>S("brain_seed_starter",{...y?{api_key:y}:{},instance_id:e,auto:!0},{seeded_count:n,auto:!0})})}function Te(){return re()&&Bn()}const to=7e3,oo=12e3,no=18e4;let ve=null;function Ee(e){return ve||(ve=so(e).finally(()=>{ve=null})),ve}async function so(e){try{if(!y){const t=Date.now()+e;for(;!Le()&&$n({})&&Date.now()<t;)await new Promise(o=>setTimeout(o,wn));const s=Le();if(!s)return!1;y=s,le(s);const n=pt();n&&!N&&(N=n,Q=0),await ue(s),n&&await pe(n),S("instant_trial_started",{tool:"sessionstart-hook",...n?{instance_id:n}:{}})}if(re()&&gt()){const t=xn();t&&(Ne(t.instanzId,re(),Te()),Ce(t.instanzId,t.projekt||void 0,t.vomHook))}}catch{}return!!y}function io(){if(!ft(process.env))return;const e=Date.now()+no,t=setInterval(()=>{if(Date.now()>e){clearInterval(t);return}y&&!gt()||Ee(0)},500);t.unref(),Ee(0)}async function ro(){if(!y)return"";try{const e=await fetch(`${x}/api/v1/instances/auto`,{method:"POST",headers:{Authorization:`Bearer ${y}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(12e3)});if(e.ok){const t=await e.json(),s=t.instance?.id??t.instance_id;if(s)return s}else fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify({event:"auto_provision_failed",status:e.status,version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{})}catch{fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:"auto_provision_failed",status:0,reason:"network_error",version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{})}return""}async function Xe(){if(N)return N;if(!y)return"";if(de)return de;const e=Date.now();if(Q>0&&e-Q<3e4)return"";Q=e,de=(async()=>{try{const t=await fetch(`${x}/api/v1/instances`,{headers:{Authorization:`Bearer ${y}`,Accept:"application/json"},signal:AbortSignal.timeout(5e3)});if(t.ok){const n=(await t.json())?.data??[],o=n.find(r=>r.status==="running")??n.find(r=>r.status==="provisioning");if(o)return N=o.id,Q=0,N;const a=await ro();if(a)return N=a,Q=0,Ne(a,re(),Te()),pe(a),Ce(a),N}}catch{}return""})();try{return await de}finally{de=null}}let Ie=0;const ao=6e4;let ae="";async function ue(e){try{const{writeFile:t,mkdir:s,readFile:n}=await import("node:fs/promises"),{existsSync:o}=await import("node:fs"),{resolve:a,dirname:r}=await import("node:path"),c=process.env.HOME??process.env.USERPROFILE??process.env.APPDATA??"";if(!c)return;const u=a(c,".claude","mcp.json");let g={};if(o(u))try{g=JSON.parse(await n(u,"utf-8"))}catch{}g.mcpServers??={};const d=g.mcpServers.cachly;if(d)d.env??={},d.env.CACHLY_JWT=e;else{const v={};v.CACHLY_JWT=e,g.mcpServers.cachly={command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:v}}await s(r(u),{recursive:!0}),await t(u,JSON.stringify(g,null,2),"utf-8");const m=[a(c,".cursor","mcp.json"),a(c,".codeium","windsurf","mcp_config.json")];await Promise.allSettled(m.map(async v=>{if(o(v))try{const w=await n(v,"utf-8"),b=JSON.parse(w),l=b?.mcpServers?.cachly;l?.env&&(l.env.CACHLY_JWT=e,await t(v,JSON.stringify(b,null,2),"utf-8"))}catch{}}))}catch{}}async function pe(e){if(e){ut(e,{apiKey:y});try{const{writeFile:t,readFile:s}=await import("node:fs/promises"),{existsSync:n}=await import("node:fs"),{resolve:o}=await import("node:path"),a=process.env.HOME??process.env.USERPROFILE??process.env.APPDATA??"";if(!a)return;const r=[o(a,".claude","mcp.json"),o(a,".cursor","mcp.json"),o(a,".codeium","windsurf","mcp_config.json")];await Promise.allSettled(r.map(async c=>{if(n(c))try{const u=JSON.parse(await s(c,"utf-8")),g=u?.mcpServers?.cachly;g?.env&&(g.env.CACHLY_BRAIN_INSTANCE_ID=e,await t(c,JSON.stringify(u,null,2),"utf-8"))}catch{}}))}catch{}}}async function co(){try{const e=await fetch(`${x}/api/v1/api-keys`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify({name:"cachly-mcp-selfheal",scope:"read_write"}),signal:AbortSignal.timeout(8e3)});if(!e.ok)return!1;const t=await e.json();return t.key?(y=t.key,le(t.key),ue(t.key),S("auth_self_healed",{reason:"near_expiry_refresh"}),!0):!1}catch{return!1}}async function Re(){const e=Se(y),t=Ue(e);if(t==="none")return ae="",!0;if(t==="refresh"){const s=Date.now();return s-Ie>=ao&&(Ie=s,await co())?(ae="",!0):(ae=e.message,e.usable)}return ae=e.message,!1}function ce(e,t){const s=(r,c)=>{try{t?.(r,c)}catch{}},n=r=>{r?s(!1,r.code??r.message):s(!0)},o=Fo(e);if(!o){s(!1,"keine-http-adresse");return}const a=Wo(o);try{process.platform==="win32"?Ae("cmd",["/c","start","",a],{windowsHide:!0},n):process.platform==="darwin"?Ae("open",[o],n):Ae("xdg-open",[o],n)}catch(r){s(!1,r instanceof Error?r.message:"unknown")}}let G=null;async function lo(){try{const e=await fetch(`${x}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({}),signal:AbortSignal.timeout(8e3)});if(e.ok){const t=await e.json(),s=t.verification_uri||R("/device","ambient-signin"),n=`${s}${s.includes("?")?"&":"?"}code=${encodeURIComponent(t.user_code)}`;return{deviceCode:t.device_code,userCode:t.user_code,verifyUrl:n,pollInterval:(t.interval??5)*1e3,deadline:Date.now()+600*1e3,polling:!1}}}catch{}try{const t=await fetch("https://auth.cachly.dev/realms/cachly/protocol/openid-connect/auth/device",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"client_id=cachly-cli&scope=openid",signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;const s=await t.json();return{deviceCode:s.device_code,userCode:s.user_code,verifyUrl:s.verification_uri_complete,pollInterval:(s.interval??5)*1e3,deadline:Date.now()+600*1e3,polling:!1}}catch{return null}}async function uo(e){if(Date.now()>e.deadline)return"expired";const t=async()=>{const n=await fetch(`${x}/auth/device/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:e.deviceCode}),signal:AbortSignal.timeout(8e3)});return n.ok?n.json():null},s=async()=>(await fetch("https://auth.cachly.dev/realms/cachly/protocol/openid-connect/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`client_id=cachly-cli&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${e.deviceCode}`,signal:AbortSignal.timeout(8e3)})).json();try{let n=await t().catch(()=>null);if(n||(n=await s().catch(()=>null)),!n)return"pending";if(n.access_token){let o=n.access_token;try{const a=await fetch(`${x}/api/v1/api-keys`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({name:"cachly-mcp-smithery",scope:"read_write"}),signal:AbortSignal.timeout(8e3)});if(a.ok){const r=await a.json();r.key&&(o=r.key)}}catch{}return y=o,le(o),G=null,S("device_flow_completed"),ue(o),X(o),Q=0,await Xe(),N&&pe(N),"done"}return n.error==="slow_down"&&(e.pollInterval=Math.min(e.pollInterval+2e3,15e3)),"pending"}catch{return"pending"}}async function et(e){if(e.polling)return"pending";e.polling=!0;try{return await uo(e)}finally{e.polling=!1}}function po(e,t=et,s=o=>G===o,n=mo){const o=async()=>{if(!s(e))return;let a="pending";try{a=await t(e)}catch{}if(a!=="done"){if(a==="expired"){s(e)&&(G=null);return}s(e)&&n(o,e.pollInterval)}};n(o,e.pollInterval)}function mo(e,t){const s=setTimeout(e,t);typeof s.unref=="function"&&s.unref()}import{setEmbedJwt as le,computeEmbedding as tt,hasEmbedProvider as go,EMBED_PROVIDER as ot}from"./embeddings.js";import{ZWEIT_MODELL as fo}from"./rangfolge-stellschrauben.js";import{tokenize as ho,splitMultiQuery as yo,levenshtein as bo,recencyBoost as vo,extractTimestamp as wo,STOPWORDS as xo,katakanaToRomaji as _o,arabicLightStem as $o,expandCrossLingual as ko,CROSS_LINGUAL_MAP as So,ZERO_RESULTS_LOG as Ao,zeroResultsTotal as Co}from"./search.js";const se=new Map;function To(e){return e<=1?200:null}async function H(e){if(!e)throw new oe(ie.InvalidRequest,`\u23F3 Your Brain instance is still being set up.
2
+ import{setzeClientKennung as Ht}from"./herkunft.js";import{jwtExpiryMs as Mt,checkJwt as Dt,handleApiError as Ut,diagnoseAuth as Se,planAuthHeal as Ue,readClientCredentialsFromEnv as zt,buildClientCredentialsBody as Jt,clientCredentialsTokenUrl as Ft}from"./auth.js";import{cachlyUrl as R}from"./cachly-url.js";import{nutzungsSchluessel as Wt}from"./werkzeug-nutzung.js";import{istLieferung as Yt,lieferSchluessel as Kt,tokenDerAntwort as qt,hatTreffer as Gt,trefferSchluessel as ze}from"./geliefert.js";import{notify as Je}from"./notifier.js";import{fileURLToPath as Fe}from"node:url";import{createRequire as Vt}from"node:module";import{execFile as Ae}from"node:child_process";const Zt=process.argv[1]!=null&&(process.argv[1]===Fe(import.meta.url)||process.argv[1].endsWith("/dist/index.js")||process.argv[1].endsWith("/src/index.ts"));import{Server as We}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Qt}from"@modelcontextprotocol/sdk/server/stdio.js";import{StreamableHTTPServerTransport as Xt}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{CallToolRequestSchema as Ye,ErrorCode as ie,ListPromptsRequestSchema as Ke,ListResourceTemplatesRequestSchema as qe,ListResourcesRequestSchema as Ge,ListToolsRequestSchema as Ve,McpError as oe}from"@modelcontextprotocol/sdk/types.js";import{Redis as Ze}from"ioredis";let x=process.env.CACHLY_API_URL??"https://api.cachly.dev",y=Be()??"";le(y);const Ps=process.env.CACHLY_EMBED_MODEL??"",D=(()=>{try{const e=Vt(import.meta.url);for(const t of["../../package.json","../package.json"])try{const s=e(t);if(s.name==="@cachly-dev/mcp-server"&&s.version)return s.version}catch{}}catch{}return"0.10.100"})(),eo=Number(process.env.CACHLY_PROVISION_TIMEOUT_MS??9e4);let N=xe(process.env.CACHLY_BRAIN_INSTANCE_ID);function ne(){return y||Be()||""}function F(){return xe(process.env.CACHLY_BRAIN_INSTANCE_ID)||pt()||""}let Q=0,de=null,Qe=!1;async function to(){let e=[];try{V.getClientCapabilities()?.roots&&(e=(await V.listRoots(void 0,{timeout:3e3})).roots)}catch{}return Bn({wurzeln:e,env:process.env,cwd:process.cwd(),dateiPfad:Fe})}function re(){return Qe?ft(process.env):!1}function Ce(e,t,s){!e||!re()||En({instanzId:e,vomHook:s,verbindung:H,ausGit:n=>je("brain_from_git",n,H,P),projektOrdner:t?async()=>t:to,einbetten:n=>tt(n,{geduld:"lang"}),hatEinbettung:fo,einbettenZweit:n=>tt(n,{geduld:"lang",modell:ho}),melde:n=>S("brain_from_git",{...y?{api_key:y}:{},instance_id:e,total:n,auto:"startwissen"}),startvorrat:n=>Oe("brain_seed_starter",n,H,P),meldeStartvorrat:n=>S("brain_seed_starter",{...y?{api_key:y}:{},instance_id:e,auto:!0},{seeded_count:n,auto:!0})})}function Te(){return re()&&Ln()}const oo=7e3,no=12e3,so=18e4;let ve=null;function Ee(e){return ve||(ve=io(e).finally(()=>{ve=null})),ve}async function io(e){try{if(!y){const t=Date.now()+e;for(;!Le()&&kn({})&&Date.now()<t;)await new Promise(o=>setTimeout(o,xn));const s=Le();if(!s)return!1;y=s,le(s);const n=pt();n&&!N&&(N=n,Q=0),await ue(s),n&&await pe(n),S("instant_trial_started",{tool:"sessionstart-hook",...n?{instance_id:n}:{}})}if(re()&&gt()){const t=_n();t&&(Ne(t.instanzId,re(),Te()),Ce(t.instanzId,t.projekt||void 0,t.vomHook))}}catch{}return!!y}function ro(){if(!ft(process.env))return;const e=Date.now()+so,t=setInterval(()=>{if(Date.now()>e){clearInterval(t);return}y&&!gt()||Ee(0)},500);t.unref(),Ee(0)}async function ao(){if(!y)return"";try{const e=await fetch(`${x}/api/v1/instances/auto`,{method:"POST",headers:{Authorization:`Bearer ${y}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(12e3)});if(e.ok){const t=await e.json(),s=t.instance?.id??t.instance_id;if(s)return s}else fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify({event:"auto_provision_failed",status:e.status,version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{})}catch{fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:"auto_provision_failed",status:0,reason:"network_error",version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{})}return""}async function Xe(){if(N)return N;if(!y)return"";if(de)return de;const e=Date.now();if(Q>0&&e-Q<3e4)return"";Q=e,de=(async()=>{try{const t=await fetch(`${x}/api/v1/instances`,{headers:{Authorization:`Bearer ${y}`,Accept:"application/json"},signal:AbortSignal.timeout(5e3)});if(t.ok){const n=(await t.json())?.data??[],o=n.find(r=>r.status==="running")??n.find(r=>r.status==="provisioning");if(o)return N=o.id,Q=0,N;const a=await ao();if(a)return N=a,Q=0,Ne(a,re(),Te()),pe(a),Ce(a),N}}catch{}return""})();try{return await de}finally{de=null}}let Ie=0;const co=6e4;let ae="";async function ue(e){try{const{writeFile:t,mkdir:s,readFile:n}=await import("node:fs/promises"),{existsSync:o}=await import("node:fs"),{resolve:a,dirname:r}=await import("node:path"),c=process.env.HOME??process.env.USERPROFILE??process.env.APPDATA??"";if(!c)return;const u=a(c,".claude","mcp.json");let g={};if(o(u))try{g=JSON.parse(await n(u,"utf-8"))}catch{}g.mcpServers??={};const d=g.mcpServers.cachly;if(d)d.env??={},d.env.CACHLY_JWT=e;else{const v={};v.CACHLY_JWT=e,g.mcpServers.cachly={command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:v}}await s(r(u),{recursive:!0}),await t(u,JSON.stringify(g,null,2),"utf-8");const m=[a(c,".cursor","mcp.json"),a(c,".codeium","windsurf","mcp_config.json")];await Promise.allSettled(m.map(async v=>{if(o(v))try{const w=await n(v,"utf-8"),b=JSON.parse(w),l=b?.mcpServers?.cachly;l?.env&&(l.env.CACHLY_JWT=e,await t(v,JSON.stringify(b,null,2),"utf-8"))}catch{}}))}catch{}}async function pe(e){if(e){ut(e,{apiKey:y});try{const{writeFile:t,readFile:s}=await import("node:fs/promises"),{existsSync:n}=await import("node:fs"),{resolve:o}=await import("node:path"),a=process.env.HOME??process.env.USERPROFILE??process.env.APPDATA??"";if(!a)return;const r=[o(a,".claude","mcp.json"),o(a,".cursor","mcp.json"),o(a,".codeium","windsurf","mcp_config.json")];await Promise.allSettled(r.map(async c=>{if(n(c))try{const u=JSON.parse(await s(c,"utf-8")),g=u?.mcpServers?.cachly;g?.env&&(g.env.CACHLY_BRAIN_INSTANCE_ID=e,await t(c,JSON.stringify(u,null,2),"utf-8"))}catch{}}))}catch{}}}async function lo(){try{const e=await fetch(`${x}/api/v1/api-keys`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify({name:"cachly-mcp-selfheal",scope:"read_write"}),signal:AbortSignal.timeout(8e3)});if(!e.ok)return!1;const t=await e.json();return t.key?(y=t.key,le(t.key),ue(t.key),S("auth_self_healed",{reason:"near_expiry_refresh"}),!0):!1}catch{return!1}}async function Re(){const e=Se(y),t=Ue(e);if(t==="none")return ae="",!0;if(t==="refresh"){const s=Date.now();return s-Ie>=co&&(Ie=s,await lo())?(ae="",!0):(ae=e.message,e.usable)}return ae=e.message,!1}function ce(e,t){const s=(r,c)=>{try{t?.(r,c)}catch{}},n=r=>{r?s(!1,r.code??r.message):s(!0)},o=Wo(e);if(!o){s(!1,"keine-http-adresse");return}const a=Yo(o);try{process.platform==="win32"?Ae("cmd",["/c","start","",a],{windowsHide:!0},n):process.platform==="darwin"?Ae("open",[o],n):Ae("xdg-open",[o],n)}catch(r){s(!1,r instanceof Error?r.message:"unknown")}}let G=null;async function uo(){try{const e=await fetch(`${x}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({}),signal:AbortSignal.timeout(8e3)});if(e.ok){const t=await e.json(),s=t.verification_uri||R("/device","ambient-signin"),n=`${s}${s.includes("?")?"&":"?"}code=${encodeURIComponent(t.user_code)}`;return{deviceCode:t.device_code,userCode:t.user_code,verifyUrl:n,pollInterval:(t.interval??5)*1e3,deadline:Date.now()+600*1e3,polling:!1}}}catch{}try{const t=await fetch("https://auth.cachly.dev/realms/cachly/protocol/openid-connect/auth/device",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"client_id=cachly-cli&scope=openid",signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;const s=await t.json();return{deviceCode:s.device_code,userCode:s.user_code,verifyUrl:s.verification_uri_complete,pollInterval:(s.interval??5)*1e3,deadline:Date.now()+600*1e3,polling:!1}}catch{return null}}async function po(e){if(Date.now()>e.deadline)return"expired";const t=async()=>{const n=await fetch(`${x}/auth/device/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:e.deviceCode}),signal:AbortSignal.timeout(8e3)});return n.ok?n.json():null},s=async()=>(await fetch("https://auth.cachly.dev/realms/cachly/protocol/openid-connect/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`client_id=cachly-cli&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${e.deviceCode}`,signal:AbortSignal.timeout(8e3)})).json();try{let n=await t().catch(()=>null);if(n||(n=await s().catch(()=>null)),!n)return"pending";if(n.access_token){let o=n.access_token;try{const a=await fetch(`${x}/api/v1/api-keys`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({name:"cachly-mcp-smithery",scope:"read_write"}),signal:AbortSignal.timeout(8e3)});if(a.ok){const r=await a.json();r.key&&(o=r.key)}}catch{}return y=o,le(o),G=null,S("device_flow_completed"),ue(o),X(o),Q=0,await Xe(),N&&pe(N),"done"}return n.error==="slow_down"&&(e.pollInterval=Math.min(e.pollInterval+2e3,15e3)),"pending"}catch{return"pending"}}async function et(e){if(e.polling)return"pending";e.polling=!0;try{return await po(e)}finally{e.polling=!1}}function mo(e,t=et,s=o=>G===o,n=go){const o=async()=>{if(!s(e))return;let a="pending";try{a=await t(e)}catch{}if(a!=="done"){if(a==="expired"){s(e)&&(G=null);return}s(e)&&n(o,e.pollInterval)}};n(o,e.pollInterval)}function go(e,t){const s=setTimeout(e,t);typeof s.unref=="function"&&s.unref()}import{setEmbedJwt as le,computeEmbedding as tt,hasEmbedProvider as fo,EMBED_PROVIDER as ot}from"./embeddings.js";import{ZWEIT_MODELL as ho}from"./rangfolge-stellschrauben.js";import{tokenize as yo,splitMultiQuery as bo,levenshtein as vo,recencyBoost as wo,extractTimestamp as xo,STOPWORDS as _o,katakanaToRomaji as $o,arabicLightStem as ko,expandCrossLingual as So,CROSS_LINGUAL_MAP as Ao,ZERO_RESULTS_LOG as Co,zeroResultsTotal as To}from"./search.js";const se=new Map;function Eo(e){return e<=1?200:null}async function H(e){if(!e)throw new oe(ie.InvalidRequest,`\u23F3 Your Brain instance is still being set up.
3
3
 
4
4
  This usually resolves in a few seconds \u2014 just call the tool again and it will connect automatically.
5
5
 
6
6
  If it keeps happening:
7
7
  \u2022 Run \`list_instances\` to check your instances, or
8
8
  \u2022 Set CACHLY_BRAIN_INSTANCE_ID in your MCP config, or
9
- \u2022 Reach us at support@cachly.dev.`);const t=se.get(e);return t||An(Eo,e,()=>Io(e))}const Eo=new Map;async function Io(e){W("verbindung: Instanz abfragen");let t=await P(`/api/v1/instances/${e}`);if(W(`verbindung: Instanz ${t.status}`),t=await Sn(t,()=>P(`/api/v1/instances/${e}`),{fristMs:Xt,melde:r=>W(`verbindung: Instanz ${r.status}`)}),t.status!=="running"){fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json",...y?{Authorization:`Bearer ${y}`}:{}},body:JSON.stringify({event:"instance_not_reachable",instance_id:e,status:t.status,version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{});let r;throw t.status==="provisioning"?r=`\u23F3 Brain instance "${t.name}" is still starting up.
9
+ \u2022 Reach us at support@cachly.dev.`);const t=se.get(e);return t||Cn(Io,e,()=>Ro(e))}const Io=new Map;async function Ro(e){W("verbindung: Instanz abfragen");let t=await P(`/api/v1/instances/${e}`);if(W(`verbindung: Instanz ${t.status}`),t=await An(t,()=>P(`/api/v1/instances/${e}`),{fristMs:eo,melde:r=>W(`verbindung: Instanz ${r.status}`)}),t.status!=="running"){fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json",...y?{Authorization:`Bearer ${y}`}:{}},body:JSON.stringify({event:"instance_not_reachable",instance_id:e,status:t.status,version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{});let r;throw t.status==="provisioning"?r=`\u23F3 Brain instance "${t.name}" is still starting up.
10
10
 
11
11
  First-time provisioning typically takes 1\u20133 minutes. Please retry in a moment \u2014 the instance will be ready soon.`:t.status==="failed"?r=`\u274C Brain instance "${t.name}" failed to start.
12
12
 
@@ -18,20 +18,20 @@ Update your payment method at: ${R("/billing","upgrade")}`:t.status==="pending_p
18
18
  Complete your checkout at: ${R("/instances","upgrade")}`:r=`Brain instance "${t.name}" is not reachable (status: ${t.status}).
19
19
 
20
20
  \u2022 View your instance at: ${R("/instances","instance-error")}
21
- \u2022 Run \`get_api_status\` for a full diagnostic.`,new oe(ie.InvalidRequest,r)}if(!t.host||!t.port)throw new oe(ie.InternalError,`Instance "${t.name}" is running but has no host/port \u2014 contact support@cachly.dev.`);let s,n=t.tls_enabled!==!1;try{const r=await P(`/api/v1/instances/${e}/connection`);s=r.password??void 0,n=r.tls_enabled!==!1,W("verbindung: Zugangsdaten da")}catch(r){const c=r instanceof Error?r.message:String(r);if(c.includes("401")||c.includes("403")||c.includes("Unauthorized"))throw new oe(ie.InvalidRequest,"API key rejected \u2014 run `cachly setup` to refresh your credentials.")}const o=new Ze({host:t.host,port:t.port,password:s||void 0,commandTimeout:8e3,...n?{tls:{}}:{},lazyConnect:!0,enableReadyCheck:!0,connectTimeout:8e3,retryStrategy:To,maxRetriesPerRequest:1,keepAlive:15e3}),a=()=>{se.delete(e),o.disconnect()};o.on("error",a),o.on("end",a);try{await o.connect()}catch(r){o.disconnect();const c=r instanceof Error?r.message:String(r);throw fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json",...y?{Authorization:`Bearer ${y}`}:{}},body:JSON.stringify({event:"redis_connect_failed",instance_id:e,host:t.host,port:t.port,error:c,version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{}),new oe(ie.InternalError,`Could not reach Brain instance "${t.name}" (${t.host}:${t.port}).
21
+ \u2022 Run \`get_api_status\` for a full diagnostic.`,new oe(ie.InvalidRequest,r)}if(!t.host||!t.port)throw new oe(ie.InternalError,`Instance "${t.name}" is running but has no host/port \u2014 contact support@cachly.dev.`);let s,n=t.tls_enabled!==!1;try{const r=await P(`/api/v1/instances/${e}/connection`);s=r.password??void 0,n=r.tls_enabled!==!1,W("verbindung: Zugangsdaten da")}catch(r){const c=r instanceof Error?r.message:String(r);if(c.includes("401")||c.includes("403")||c.includes("Unauthorized"))throw new oe(ie.InvalidRequest,"API key rejected \u2014 run `cachly setup` to refresh your credentials.")}const o=new Ze({host:t.host,port:t.port,password:s||void 0,commandTimeout:8e3,...n?{tls:{}}:{},lazyConnect:!0,enableReadyCheck:!0,connectTimeout:8e3,retryStrategy:Eo,maxRetriesPerRequest:1,keepAlive:15e3}),a=()=>{se.delete(e),o.disconnect()};o.on("error",a),o.on("end",a);try{await o.connect()}catch(r){o.disconnect();const c=r instanceof Error?r.message:String(r);throw fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json",...y?{Authorization:`Bearer ${y}`}:{}},body:JSON.stringify({event:"redis_connect_failed",instance_id:e,host:t.host,port:t.port,error:c,version:D}),signal:AbortSignal.timeout(3e3)}).catch(()=>{}),new oe(ie.InternalError,`Could not reach Brain instance "${t.name}" (${t.host}:${t.port}).
22
22
 
23
23
  \u2022 Check your network connection
24
24
  \u2022 Run \`get_api_status\` to verify instance status
25
- \u2022 Technical detail: ${c}`)}return se.set(e,o),W("verbindung: Valkey verbunden"),o}async function P(e,t={},s=!1){s||Ue(Se(y))!=="none"&&await Re(),Mt(y);const n=Number(process.env.CACHLY_API_TIMEOUT_MS??15e3),o=await fetch(`${x}${e}`,{...t,signal:AbortSignal.timeout(n),headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`,...t.headers}});if(!o.ok){if(o.status===401&&!s&&(Ie=0,await Re()))return P(e,t,!0);const r=(await o.json().catch(()=>({error:o.statusText}))).error??o.statusText;Dt(o.status,r)}if(o.status!==204)return o.json()}import{handleBrainTool as Ro}from"./handlers/brain.js";import{handleContextTool as jo}from"./handlers/context.js";import{handleInstanceTool as Bo}from"./handlers/instances.js";import{handleCacheTool as Lo}from"./handlers/cache.js";import{handleTeamTool as No}from"./handlers/team.js";import{handleRoadmapTool as Oo}from"./handlers/roadmap.js";import{handleAdvancedTool as Po}from"./handlers/advanced.js";import{handleSyndicateTool as Ho}from"./handlers/syndicate.js";import{handleFedbrainTool as je,_lastBrainFromGitCounts as we}from"./handlers/fedbrain.js";import{extractFirstRecallProof as Mo,renderFirstRecallProof as Do}from"./first-recall-proof.js";import{installClsPostCommitHook as nt,CLS_HOOK_VERSION as st}from"./cls-hook.js";import{CLAUDE_MD_MARKER_START as it,CLAUDE_MD_MARKER_END as rt,EDITOR_FILES as me,ANWEISUNGS_DATEIEN as Uo,setzeAnweisungsblock as zo}from"./projektdateien.js";import{uninstallBefehl as Jo}from"./uninstall.js";import{gitLesen as at}from"./git-ohne-shell.js";import{browserAdresse as Fo,fuerCmd as Wo}from"./browser-adresse.js";import{installAmbientHooks as ct,installCursorHooks as Yo,installCopilotHooks as Ko,installCodexHooks as qo,installGeminiHooks as Go,AMBIENT_HOOK_VERSION as lt,COPILOT_HOOKS_DATEI as Vo,CODEX_HOOKS_DATEI as Zo,GEMINI_SETTINGS_DATEI as Qo}from"./ambient-hooks.js";import{hookHauptlauf as Xo,auffrischenHauptlauf as en,startwissenImportHauptlauf as tn}from"./ambient-hook.js";import{STARTWISSEN_IMPORT as on}from"./startwissen-hook.js";import{rahmeAntwort as nn}from"./antwort-rahmen.js";import{appendLedgerEntry as sn,readLedger as dt,defaultLedgerPath as rn,reportLedgerEntry as an,teileNachInstanz as cn}from"./ambient-ledger.js";import{installCodexMcp as ln,installGeminiMcp as dn}from"./editor-mcp-eintrag.js";import{resolveApiKey as Be,saveApiKey as X,saveInstanceId as ut,readSavedApiKey as Le,readInstanceId as pt}from"./credentials.js";import{KEYCLOAK_BASIS as mt,istTestSchluessel as un,waehleTestSchluessel as pn,starteKeycloakAnmeldung as mn,warteAufKeycloakAnmeldung as gn,uebernimm as fn,brainImKonto as hn,erfolgsText as yn,fehlerText as bn,ersetzeSchluessel as vn}from"./uebernahme.js";import{WARTE_TAKT_MS as wn,auftragLiegt as gt,nimmAuftrag as xn,sichereKonto as _n,sperreLiegt as $n,startwissenEingeschaltet as ft}from"./konto-anlegen.js";import{sichereZugang as kn,echterWert as xe}from"./zugang.js";import{zeitpunkt as W}from"./zeitmessung.js";import{warteAufLaufendeInstanz as Sn,gemeinsamerAufbau as An}from"./instanz-warten.js";import{merkeNeuesBrain as Ne,startetLautLetztemBlick as Cn}from"./leeres-brain.js";import{starteStartwissen as Tn,startwissenGestartet as En,beanspruchStartwissen as In,holeStartwissenHinweis as Rn,waehleProjektOrdner as jn,startvorratErlaubt as Bn}from"./startwissen.js";import{eigeneFassung as Ln,pruefeNeueFassung as Nn,holeNeueFassungHinweis as On}from"./neue-fassung.js";import{beiStdinEnde as Pn}from"./stdin-ende.js";import{merkeWerkzeugAufruf as Hn,starteBrandWachhund as Mn}from"./brand-wachhund.js";import{sichtbareWerkzeuge as Dn,VERTEILER as ht}from"./werkzeug-auswahl.js";import{buildLessonsMarkdown as Un,buildLessonsJsonl as zn,vonRohLektion as Jn}from"./brain-export.js";import{detectEditor as Fn}from"./editor.js";import{milestoneSent as yt,markMilestoneSent as bt}from"./funnel-milestones.js";import{kostprobeUebrig as Wn,kostprobeVerbrauchen as Yn,kostprobeHinweis as Kn,schrankeNachKostproben as qn}from"./kostprobe.js";import{netBalance as Gn}from"./ambient-recall.js";import{handleShareTool as Oe}from"./handlers/share.js";import{handleVizTool as Vn}from"./handlers/viz.js";import{commitArt as Zn,istRueckbau as Qn,zaehleLektionen as Xn}from"./commit-art.js";import{TOOLS as ge}from"./tools.js";let vt=!1,_e=!1;function wt(){return Fn()}function es(e){if(!e)return null;const t=e.match(/\*\*💡\s+([^*]+)\*\*/),s=e.match(/^>\s+(.+)$/m);if(!t||!s)return null;const n=t[1].trim(),o=s[1].trim();return!n||!o?null:{topic:n,what:o}}function ts(e){try{const t=e.split(".");if(t.length!==3)return;const s=JSON.parse(Buffer.from(t[1],"base64url").toString("utf-8"));if(!s.sub)return;let n=2166136261;for(let o=0;o<s.sub.length;o++)n^=s.sub.charCodeAt(o),n=n*16777619>>>0;return n.toString(16).padStart(8,"0")+s.sub.length.toString(16).padStart(4,"0")}catch{return}}function S(e,t,s){if(process.env.CACHLY_NO_TELEMETRY==="1")return;const n=y?ts(y):void 0;fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:e,version:D,editor:wt(),...y?{jwt:y}:{},...n?{user_fingerprint:n}:{},...s?{metrics:s}:{},...t}),signal:AbortSignal.timeout(3e3)}).catch(()=>{})}function os(e,t){return an({apiUrl:x,jwt:y,instanceId:e},t)}async function ns(e){if(!vt&&process.env.CACHLY_NO_TELEMETRY!=="1"){vt=!0;try{await fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:"first_call_no_jwt",version:D,editor:wt(),tool:e}),signal:AbortSignal.timeout(3e3)})}catch{}}}const ss=new Set(["causal_trace","brain_predict","brain_plan","brain_predict_failures","team_learn","team_confirm","team_recall","team_synthesize","team_crystallize","global_learn","global_recall"]),is={causal_trace:"Causal root-cause tracing \u2014 follow the failure chain to the real cause, not just the symptom.",brain_predict:"Predictive risk \u2014 your Brain warns you what will break *before* you run it.",brain_plan:"Generative planning \u2014 turn a goal into an ordered, risk-aware plan from your own history.",brain_predict_failures:"Failure prediction \u2014 catch CI/build breakage before it happens.",team_learn:"Team Brain \u2014 one shared memory across your whole team.",team_confirm:"Team Brain \u2014 peer + senior review that raises lesson trust.",team_recall:"Team Brain \u2014 recall everything your teammates have already solved.",team_synthesize:"Team Brain \u2014 synthesize collective knowledge across authors.",team_crystallize:"Team Brain \u2014 compress your team's wisdom into a shareable crystal.",global_learn:"Team Brain \u2014 contribute to your organization's shared memory.",global_recall:"Team Brain \u2014 recall across your entire organization."},xt=R("/billing","upgrade"),_t=new Map;async function rs(e){const t=_t.get(e);if(t&&t.expiresAt>Date.now())return t.info;try{const n=((await P(`/api/v1/instances/${e}`))?.tier??"free").toLowerCase(),o={tier:n,isFree:n==="free"||n===""};return _t.set(e,{info:o,expiresAt:Date.now()+6e4}),o}catch{return{tier:"unknown",isFree:!1}}}async function as(e,t){if(!ss.has(e)||!t)return null;const{isFree:s}=await rs(t);if(!s)return null;const n=is[e]??"A Premium intelligence feature.";if(Wn(e)){const o=Yn(e);return{kostprobe:Kn(e,o,xt)}}return{sperre:qn(e,n,xt)}}async function cs(e,t){try{await(await H(e)).hincrby(Ft(e),t,1)}catch{}}async function ls(e,t,s){if(Wt(t))try{const n=await H(e),o=qt(s);await n.incrby(Yt(e),o?Kt(s):0),await n.hincrby(ze(e),"abrufe",1),o&&await n.hincrby(ze(e),"treffer",1)}catch{}}async function ds(e){const t=await lo();return t?(G=t,S("device_flow_started",{tool:e}),ce(t.verifyUrl,(s,n)=>{if(s){S("device_browser_opened",{stelle:"tool"});return}S("device_browser_failed",{reason:n??"unknown",stelle:"tool"}),process.stderr.write(`
25
+ \u2022 Technical detail: ${c}`)}return se.set(e,o),W("verbindung: Valkey verbunden"),o}async function P(e,t={},s=!1){s||Ue(Se(y))!=="none"&&await Re(),Dt(y);const n=Number(process.env.CACHLY_API_TIMEOUT_MS??15e3),o=await fetch(`${x}${e}`,{...t,signal:AbortSignal.timeout(n),headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`,...t.headers}});if(!o.ok){if(o.status===401&&!s&&(Ie=0,await Re()))return P(e,t,!0);const r=(await o.json().catch(()=>({error:o.statusText}))).error??o.statusText;Ut(o.status,r)}if(o.status!==204)return o.json()}import{handleBrainTool as jo}from"./handlers/brain.js";import{handleContextTool as Bo}from"./handlers/context.js";import{handleInstanceTool as Lo}from"./handlers/instances.js";import{handleCacheTool as No}from"./handlers/cache.js";import{handleTeamTool as Oo}from"./handlers/team.js";import{handleRoadmapTool as Po}from"./handlers/roadmap.js";import{handleAdvancedTool as Ho}from"./handlers/advanced.js";import{handleSyndicateTool as Mo}from"./handlers/syndicate.js";import{handleFedbrainTool as je,_lastBrainFromGitCounts as we}from"./handlers/fedbrain.js";import{extractFirstRecallProof as Do,renderFirstRecallProof as Uo}from"./first-recall-proof.js";import{installClsPostCommitHook as nt,CLS_HOOK_VERSION as st}from"./cls-hook.js";import{CLAUDE_MD_MARKER_START as it,CLAUDE_MD_MARKER_END as rt,EDITOR_FILES as me,ANWEISUNGS_DATEIEN as zo,setzeAnweisungsblock as Jo}from"./projektdateien.js";import{uninstallBefehl as Fo}from"./uninstall.js";import{gitLesen as at}from"./git-ohne-shell.js";import{browserAdresse as Wo,fuerCmd as Yo}from"./browser-adresse.js";import{installAmbientHooks as ct,installCursorHooks as Ko,installCopilotHooks as qo,installCodexHooks as Go,installGeminiHooks as Vo,AMBIENT_HOOK_VERSION as lt,COPILOT_HOOKS_DATEI as Zo,CODEX_HOOKS_DATEI as Qo,GEMINI_SETTINGS_DATEI as Xo}from"./ambient-hooks.js";import{hookHauptlauf as en,auffrischenHauptlauf as tn,startwissenImportHauptlauf as on}from"./ambient-hook.js";import{STARTWISSEN_IMPORT as nn}from"./startwissen-hook.js";import{rahmeAntwort as sn}from"./antwort-rahmen.js";import{appendLedgerEntry as rn,readLedger as dt,defaultLedgerPath as an,reportLedgerEntry as cn,teileNachInstanz as ln}from"./ambient-ledger.js";import{installCodexMcp as dn,installGeminiMcp as un}from"./editor-mcp-eintrag.js";import{resolveApiKey as Be,saveApiKey as X,saveInstanceId as ut,readSavedApiKey as Le,readInstanceId as pt}from"./credentials.js";import{KEYCLOAK_BASIS as mt,istTestSchluessel as pn,waehleTestSchluessel as mn,starteKeycloakAnmeldung as gn,warteAufKeycloakAnmeldung as fn,uebernimm as hn,brainImKonto as yn,erfolgsText as bn,fehlerText as vn,ersetzeSchluessel as wn}from"./uebernahme.js";import{WARTE_TAKT_MS as xn,auftragLiegt as gt,nimmAuftrag as _n,sichereKonto as $n,sperreLiegt as kn,startwissenEingeschaltet as ft}from"./konto-anlegen.js";import{sichereZugang as Sn,echterWert as xe}from"./zugang.js";import{zeitpunkt as W}from"./zeitmessung.js";import{warteAufLaufendeInstanz as An,gemeinsamerAufbau as Cn}from"./instanz-warten.js";import{merkeNeuesBrain as Ne,startetLautLetztemBlick as Tn}from"./leeres-brain.js";import{starteStartwissen as En,startwissenGestartet as In,beanspruchStartwissen as Rn,holeStartwissenHinweis as jn,waehleProjektOrdner as Bn,startvorratErlaubt as Ln}from"./startwissen.js";import{eigeneFassung as Nn,pruefeNeueFassung as On,holeNeueFassungHinweis as Pn}from"./neue-fassung.js";import{beiStdinEnde as Hn}from"./stdin-ende.js";import{merkeWerkzeugAufruf as Mn,starteBrandWachhund as Dn}from"./brand-wachhund.js";import{sichtbareWerkzeuge as Un,VERTEILER as ht}from"./werkzeug-auswahl.js";import{buildLessonsMarkdown as zn,buildLessonsJsonl as Jn,vonRohLektion as Fn}from"./brain-export.js";import{detectEditor as Wn}from"./editor.js";import{milestoneSent as yt,markMilestoneSent as bt}from"./funnel-milestones.js";import{kostprobeUebrig as Yn,kostprobeVerbrauchen as Kn,kostprobeHinweis as qn,schrankeNachKostproben as Gn}from"./kostprobe.js";import{netBalance as Vn}from"./ambient-recall.js";import{handleShareTool as Oe}from"./handlers/share.js";import{handleVizTool as Zn}from"./handlers/viz.js";import{commitArt as Qn,istRueckbau as Xn,zaehleLektionen as es}from"./commit-art.js";import{TOOLS as ge}from"./tools.js";let vt=!1,wt=!1,_e=!1;function xt(){return Wn()}function ts(e){if(!e)return null;const t=e.match(/\*\*💡\s+([^*]+)\*\*/),s=e.match(/^>\s+(.+)$/m);if(!t||!s)return null;const n=t[1].trim(),o=s[1].trim();return!n||!o?null:{topic:n,what:o}}function os(e){try{const t=e.split(".");if(t.length!==3)return;const s=JSON.parse(Buffer.from(t[1],"base64url").toString("utf-8"));if(!s.sub)return;let n=2166136261;for(let o=0;o<s.sub.length;o++)n^=s.sub.charCodeAt(o),n=n*16777619>>>0;return n.toString(16).padStart(8,"0")+s.sub.length.toString(16).padStart(4,"0")}catch{return}}function S(e,t,s){if(process.env.CACHLY_NO_TELEMETRY==="1")return;const n=y?os(y):void 0;fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:e,version:D,editor:xt(),...y?{jwt:y}:{},...n?{user_fingerprint:n}:{},...s?{metrics:s}:{},...t}),signal:AbortSignal.timeout(3e3)}).catch(()=>{})}function ns(e,t){return cn({apiUrl:x,jwt:y,instanceId:e},t)}async function ss(e){if(!vt&&process.env.CACHLY_NO_TELEMETRY!=="1"){vt=!0;try{await fetch(`${x}/api/v1/telemetry/mcp`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({event:"first_call_no_jwt",version:D,editor:xt(),tool:e}),signal:AbortSignal.timeout(3e3)})}catch{}}}const is=new Set(["causal_trace","brain_predict","brain_plan","brain_predict_failures","team_learn","team_confirm","team_recall","team_synthesize","team_crystallize","global_learn","global_recall"]),rs={causal_trace:"Causal root-cause tracing \u2014 follow the failure chain to the real cause, not just the symptom.",brain_predict:"Predictive risk \u2014 your Brain warns you what will break *before* you run it.",brain_plan:"Generative planning \u2014 turn a goal into an ordered, risk-aware plan from your own history.",brain_predict_failures:"Failure prediction \u2014 catch CI/build breakage before it happens.",team_learn:"Team Brain \u2014 one shared memory across your whole team.",team_confirm:"Team Brain \u2014 peer + senior review that raises lesson trust.",team_recall:"Team Brain \u2014 recall everything your teammates have already solved.",team_synthesize:"Team Brain \u2014 synthesize collective knowledge across authors.",team_crystallize:"Team Brain \u2014 compress your team's wisdom into a shareable crystal.",global_learn:"Team Brain \u2014 contribute to your organization's shared memory.",global_recall:"Team Brain \u2014 recall across your entire organization."},_t=R("/billing","upgrade"),$t=new Map;async function as(e){const t=$t.get(e);if(t&&t.expiresAt>Date.now())return t.info;try{const n=((await P(`/api/v1/instances/${e}`))?.tier??"free").toLowerCase(),o={tier:n,isFree:n==="free"||n===""};return $t.set(e,{info:o,expiresAt:Date.now()+6e4}),o}catch{return{tier:"unknown",isFree:!1}}}async function cs(e,t){if(!is.has(e)||!t)return null;const{isFree:s}=await as(t);if(!s)return null;const n=rs[e]??"A Premium intelligence feature.";if(Yn(e)){const o=Kn(e);return{kostprobe:qn(e,o,_t)}}return{sperre:Gn(e,n,_t)}}async function ls(e,t){try{await(await H(e)).hincrby(Wt(e),t,1)}catch{}}async function ds(e,t,s){if(Yt(t))try{const n=await H(e),o=Gt(s);await n.incrby(Kt(e),o?qt(s):0),await n.hincrby(ze(e),"abrufe",1),o&&await n.hincrby(ze(e),"treffer",1)}catch{}}async function us(e){const t=await uo();return t?(G=t,S("device_flow_started",{tool:e}),ce(t.verifyUrl,(s,n)=>{if(s){S("device_browser_opened",{stelle:"tool"});return}S("device_browser_failed",{reason:n??"unknown",stelle:"tool"}),process.stderr.write(`
26
26
  cachly: could not open a browser (${n??"unknown"}).
27
27
  cachly: open this yourself to finish signing in:
28
28
  cachly: ${t.verifyUrl}
29
29
  cachly: code ${t.userCode}
30
30
 
31
- `)}),po(t),["\u{1F9E0} **cachly AI Brain \u2014 the user must sign in once.**","","**Show the following link and code to the user verbatim \u2014 they cannot","continue without them.**","",`\u{1F449} **${t.verifyUrl}**`,"",`Code: **${t.userCode}**`,"","A browser may have opened automatically with the code pre-filled. If it","did not, the link above is the whole step.","","Once signed in, the Brain activates on its own \u2014 no further action, no","restart. The next request arrives brain-powered.","",`\u2728 ${ge.length} MCP tools \xB7 GDPR \xB7 EU servers \xB7 free tier, no credit card`].join(`
31
+ `)}),mo(t),["\u{1F9E0} **cachly AI Brain \u2014 the user must sign in once.**","","**Show the following link and code to the user verbatim \u2014 they cannot","continue without them.**","",`\u{1F449} **${t.verifyUrl}**`,"",`Code: **${t.userCode}**`,"","A browser may have opened automatically with the code pre-filled. If it","did not, the link above is the whole step.","","Once signed in, the Brain activates on its own \u2014 no further action, no","restart. The next request arrives brain-powered.","",`\u2728 ${ge.length} MCP tools \xB7 GDPR \xB7 EU servers \xB7 free tier, no credit card`].join(`
32
32
  `)):["\u{1F9E0} **cachly AI Brain \u2014 Setup required**","","Run the setup wizard once in your terminal:"," ```"," npx @cachly-dev/mcp-server@latest autopilot"," ```","",`Or get your API key at: ${R("/setup-ai","ambient-signin")}`,"","\u2728 Free tier includes: 1 Brain instance, persistent memory, semantic search."].join(`
33
- `)}async function us(e){if(!e)return!0;if(En(e))return!1;try{return await In(await H(e),"sitzungsstart")}catch{return!0}}async function J(e,t){if(!y&&await Ee(to))return J(e,t);if(!y){if(ns(e),G){const i=await et(G);return i==="done"?J(e,t):i==="expired"?(S("device_flow_failed",{reason:"timeout"}),G=null,"\u231B **Authentication timed out.** Please call any tool again to restart the sign-in flow."):["\u23F3 **Signing you in\u2026**","",`Finish in your browser: **${G.verifyUrl}**`,`Code: **${G.userCode}**`,"","This completes automatically once you sign in \u2014 no need to come back here."].join(`
34
- `)}return kn({schluessel:y,konfigurierteInstanz:N,aufrufInstanz:t.instance_id,werkzeugName:e,holeSofortTest:async()=>{W("sofort-test: Anfang");const i=await _n({apiUrl:x,version:D,fristMs:oo}),p=i.art==="angelegt"?i.test:i.art==="vorhanden"&&i.instanzId?{apiKey:i.apiKey,instanzId:i.instanzId}:null;return W(`sofort-test: ${p?"Brain angelegt":"kein Test"}`),p&&Ne(p.instanzId,re(),Te()),p},ablagen:{setzeSchluessel:i=>{y=i},setEmbedJwt:le,saveApiKey:i=>X(i),persistApiKeyToConfig:ue,merkeInstanz:i=>{N=i,Q=0},persistInstanceIdToConfig:pe},meldeEreignis:S,werkzeug:()=>J(e,t),anmelden:()=>ds(e),neuesBrain:Ce})}if(!t.instance_id){const i=await Xe();i&&(t={...t,instance_id:i})}const s=await as(e,t.instance_id);let n="";if(s!==null){if("sperre"in s)return S("premium_gate_hit",{tool:e,instance_id:t.instance_id??""}),s.sperre;n=s.kostprobe,S("premium_taste_used",{tool:e,instance_id:t.instance_id??""})}typeof t.instance_id=="string"&&t.instance_id&&cs(t.instance_id,e),W(`werkzeug ${e}: Zugang und Tarif geklaert`);const o=await Ro(e,t,H,P);if(W(`werkzeug ${e}: Brain-Werkzeug fertig`),o!==null){typeof t.instance_id=="string"&&t.instance_id&&ls(t.instance_id,e,String(o??"")),!_e&&y&&(_e=!0,yt("first_call_success")||(bt("first_call_success"),S("first_call_success",{tool:e,instance_id:t.instance_id??N??""})));const i=t.instance_id??N??"",p=y?{api_key:y,instance_id:i}:{instance_id:i};if(e==="recall_best_solution")S("recall_best_solution",p);else if(e==="learn_from_attempts")S("learn_from_attempts",p);else if(e==="session_start"){S("session_start",p,{born_at:void 0,recalls_total:void 0,starter_seeded:!1});const h=typeof o=="object"&&o!==null&&"content"in o?JSON.stringify(o):String(o??""),E=h.includes("Welcome! Your AI Brain is live.");if(!Cn(i)){if(!E&&h.length>100)S("recall_best_solution",p);else if(E&&t.workspace_path&&await us(i))try{const j=await je("brain_from_git",{instance_id:i,repo_path:t.workspace_path,limit:50},H,P);if(j!==null){S("brain_from_git",{...p,...we??{}}),S("recall_best_solution",p);let C="";if((we?.total??0)===0)try{const L=await Oe("brain_seed_starter",{instance_id:i},H,P);L&&(S("brain_seed_starter",{...p,auto:!0},{seeded_count:16,auto:!0}),C=`
33
+ `)}async function ps(e){if(!e)return!0;if(In(e))return!1;try{return await Rn(await H(e),"sitzungsstart")}catch{return!0}}async function J(e,t){if(!y&&await Ee(oo))return J(e,t);if(!y){if(ss(e),G){const i=await et(G);return i==="done"?J(e,t):i==="expired"?(S("device_flow_failed",{reason:"timeout"}),G=null,"\u231B **Authentication timed out.** Please call any tool again to restart the sign-in flow."):["\u23F3 **Signing you in\u2026**","",`Finish in your browser: **${G.verifyUrl}**`,`Code: **${G.userCode}**`,"","This completes automatically once you sign in \u2014 no need to come back here."].join(`
34
+ `)}return Sn({schluessel:y,konfigurierteInstanz:N,aufrufInstanz:t.instance_id,werkzeugName:e,holeSofortTest:async()=>{W("sofort-test: Anfang");const i=await $n({apiUrl:x,version:D,fristMs:no}),p=i.art==="angelegt"?i.test:i.art==="vorhanden"&&i.instanzId?{apiKey:i.apiKey,instanzId:i.instanzId}:null;return W(`sofort-test: ${p?"Brain angelegt":"kein Test"}`),p&&Ne(p.instanzId,re(),Te()),p},ablagen:{setzeSchluessel:i=>{y=i},setEmbedJwt:le,saveApiKey:i=>X(i),persistApiKeyToConfig:ue,merkeInstanz:i=>{N=i,Q=0},persistInstanceIdToConfig:pe},meldeEreignis:S,werkzeug:()=>J(e,t),anmelden:()=>us(e),neuesBrain:Ce})}if(!t.instance_id){const i=await Xe();i&&(t={...t,instance_id:i})}const s=await cs(e,t.instance_id);let n="";if(s!==null){if("sperre"in s)return S("premium_gate_hit",{tool:e,instance_id:t.instance_id??""}),s.sperre;n=s.kostprobe,S("premium_taste_used",{tool:e,instance_id:t.instance_id??""})}typeof t.instance_id=="string"&&t.instance_id&&ls(t.instance_id,e),W(`werkzeug ${e}: Zugang und Tarif geklaert`);const o=await jo(e,t,H,P);if(W(`werkzeug ${e}: Brain-Werkzeug fertig`),o!==null){typeof t.instance_id=="string"&&t.instance_id&&ds(t.instance_id,e,String(o??"")),!_e&&y&&(_e=!0,yt("first_call_success")||(bt("first_call_success"),S("first_call_success",{tool:e,instance_id:t.instance_id??N??""})));const i=t.instance_id??N??"",p=y?{api_key:y,instance_id:i}:{instance_id:i};if(e==="recall_best_solution")S("recall_best_solution",p);else if(e==="learn_from_attempts")S("learn_from_attempts",p);else if(e==="session_start"){S("session_start",p,{born_at:void 0,recalls_total:void 0,starter_seeded:!1});const h=typeof o=="object"&&o!==null&&"content"in o?JSON.stringify(o):String(o??""),E=h.includes("Welcome! Your AI Brain is live.");if(!Tn(i)){if(!E&&h.length>100)S("recall_best_solution",p);else if(E&&t.workspace_path&&await ps(i))try{const j=await je("brain_from_git",{instance_id:i,repo_path:t.workspace_path,limit:50},H,P);if(j!==null){S("brain_from_git",{...p,...we??{}}),S("recall_best_solution",p);let C="";if((we?.total??0)===0)try{const L=await Oe("brain_seed_starter",{instance_id:i},H,P);L&&(S("brain_seed_starter",{...p,auto:!0},{seeded_count:16,auto:!0}),C=`
35
35
  ---
36
36
  `+String(L))}catch{}const T=String(j),$=we?.total??0,k=$>0?`
37
37
  \u2705 **Brain bootstrapped from git \u2014 ${$} lesson${$===1?"":"s"} loaded.** Your AI already knows your history.`:"";if(T||C||k)return String(o)+k+`
@@ -42,7 +42,7 @@ cachly: code ${t.userCode}
42
42
  > Run \`brain_from_git(instance_id="`+i+'", repo_path="'+String(t.workspace_path)+'")` manually to seed it.\n> Or try `brain_seed_starter` to load 16 universal engineering lessons instantly.'}}}else if(e==="session_end")S("session_end",p);else if(e==="smart_recall"){const h=typeof o=="object"&&o!==null&&"content"in o?JSON.stringify(o):String(o??""),E=h.length>50&&!h.includes("No lessons found")&&!h.includes("no lessons")&&!h.includes("No matches found");if(S("smart_recall",p,{hit:E,topic:String(t.query??"").slice(0,80)}),E&&S("recall_best_solution",p),ae&&typeof o=="string")return`> \u26A0\uFE0F **Brain auth degraded** \u2014 ${ae.split(`
43
43
  `)[0]}
44
44
 
45
- ${o}`}return o}const a=await jo(e,t,H,P);if(a!==null)return!_e&&y&&(_e=!0,yt("first_call_success")||(bt("first_call_success"),S("first_call_success",{tool:e,instance_id:t.instance_id??N??""}))),a;const r=await Bo(e,t,H,P);if(r!==null)return r;const c=await Lo(e,t,H,P);if(c!==null)return c;const u=i=>n?i+n:i,g=await No(e,t,H,P);if(g!==null)return u(g);const d=await Oo(e,t,H,P);if(d!==null)return d;const m=await Po(e,t,H,P);if(m!==null)return u(m);const v=await Ho(e,t,H,P);if(v!==null){if(e==="brain_predict"){const i=t.instance_id??N??"";S("brain_predict",y?{api_key:y,instance_id:i}:{instance_id:i})}return u(v)}const w=await Oe(e,t,H,P);if(w!==null)return w;const b=await Vn(e,t,H,P);if(b!==null)return b;const l=await je(e,t,H,P);if(l!==null){const i=t.instance_id??N??"",p=y?{api_key:y,instance_id:i}:{instance_id:i};if(e==="brain_from_git"){const h=we;S("brain_from_git",{...p,...h??{}},h?{fixes:h.fixes,features:h.features,refactors:h.refactors,total:h.total}:void 0)}else e==="brain_predict_failures"&&S("brain_predict_failures",p);return l}switch(e){case"get_api_status":{let i="unknown";try{const j=await fetch(`${x}/health`,{signal:AbortSignal.timeout(8e3)});if(j.ok){const A=await j.json();i=`${A.status??"ok"} (db: ${A.db??"?"})`}else i=`HTTP ${j.status}`}catch(j){i=`unreachable: ${j.message}`}if(!y)return["\u{1F4E1} **cachly API Status**","",` \u{1F310} API: ${x}`,` \u{1F493} Health: ${i}`," \u{1F511} Auth: \u274C CACHLY_JWT not set","",`\u{1F4A1} Get your API token at ${R("/instances","api-status")} \u2192 Settings \u2192 API Token`].join(`
45
+ ${o}`}return o}const a=await Bo(e,t,H,P);if(a!==null)return!_e&&y&&(_e=!0,yt("first_call_success")||(bt("first_call_success"),S("first_call_success",{tool:e,instance_id:t.instance_id??N??""}))),a;const r=await Lo(e,t,H,P);if(r!==null)return r;const c=await No(e,t,H,P);if(c!==null)return c;const u=i=>n?i+n:i,g=await Oo(e,t,H,P);if(g!==null)return u(g);const d=await Po(e,t,H,P);if(d!==null)return d;const m=await Ho(e,t,H,P);if(m!==null)return u(m);const v=await Mo(e,t,H,P);if(v!==null){if(e==="brain_predict"){const i=t.instance_id??N??"";S("brain_predict",y?{api_key:y,instance_id:i}:{instance_id:i})}return u(v)}const w=await Oe(e,t,H,P);if(w!==null)return w;const b=await Zn(e,t,H,P);if(b!==null)return b;const l=await je(e,t,H,P);if(l!==null){const i=t.instance_id??N??"",p=y?{api_key:y,instance_id:i}:{instance_id:i};if(e==="brain_from_git"){const h=we;S("brain_from_git",{...p,...h??{}},h?{fixes:h.fixes,features:h.features,refactors:h.refactors,total:h.total}:void 0)}else e==="brain_predict_failures"&&S("brain_predict_failures",p);return l}switch(e){case"get_api_status":{let i="unknown";try{const j=await fetch(`${x}/health`,{signal:AbortSignal.timeout(8e3)});if(j.ok){const A=await j.json();i=`${A.status??"ok"} (db: ${A.db??"?"})`}else i=`HTTP ${j.status}`}catch(j){i=`unreachable: ${j.message}`}if(!y)return["\u{1F4E1} **cachly API Status**","",` \u{1F310} API: ${x}`,` \u{1F493} Health: ${i}`," \u{1F511} Auth: \u274C CACHLY_JWT not set","",`\u{1F4A1} Get your API token at ${R("/instances","api-status")} \u2192 Settings \u2192 API Token`].join(`
46
46
  `);let p="\u274C invalid JWT format";try{const j=y.split(".");if(j.length===3){const A=JSON.parse(Buffer.from(j[1],"base64url").toString("utf-8")),C=A.sub??"(unknown)",T=A.iss??"(unknown)",$=T.includes("keycloak")?"Keycloak":"OIDC",k=A.exp?new Date(A.exp*1e3):null,L=k?k<new Date:!1;p=["\u2705 JWT decoded",` Sub (user ID): ${C}`,` Provider: ${$}`,` Issuer: ${T}`,` Expires: ${k?k.toISOString():"never"} ${L?"\u26A0\uFE0F EXPIRED \u2013 get a new token!":"\u2705"}`].join(`
47
47
  `)}}catch{p="\u274C JWT decode failed \u2013 check CACHLY_JWT format"}let h="";try{const A=(await P("/api/v1/instances")).data??[];if(A.length===0)h=`
48
48
 
@@ -53,17 +53,17 @@ ${o}`}return o}const a=await jo(e,t,H,P);if(a!==null)return!_e&&y&&(_e=!0,yt("fi
53
53
 
54
54
  \u{1F9E0} **Brain Instances:** could not fetch \u2014 ${j.message}`}await Re();const E=Se(y),O=E.state==="long_lived"?"\u{1F6E1}\uFE0F Self-healing: \u2705 long-lived API key (won't expire silently)":E.state==="healthy"?`\u{1F6E1}\uFE0F Self-healing: \u2705 token valid${E.expiresInMs?` (${Math.round(E.expiresInMs/36e5)}h left)`:""}`:E.state==="near_expiry"?"\u{1F6E1}\uFE0F Self-healing: \u267B\uFE0F token near expiry \u2014 auto-refresh attempted":`\u{1F6E1}\uFE0F Self-healing: \u26A0\uFE0F ${E.message.split(`
55
55
  `)[0]}`;return["\u{1F4E1} **cachly API Status**","",` \u{1F310} API: ${x}`,` \u{1F493} Health: ${i}`,` ${O}`,"","\u{1F511} **Auth:**",p,h].join(`
56
- `)}default:throw new $t(`Unknown tool: ${e}`)}}class $t extends Error{}const kt=`This project has a Cachly AI Brain \u2014 persistent, cross-session memory of past fixes, decisions, and gotchas for THIS codebase. Use it proactively; it makes you faster and stops you repeating known mistakes.
56
+ `)}default:throw new kt(`Unknown tool: ${e}`)}}class kt extends Error{}const St=`This project has a Cachly AI Brain \u2014 persistent, cross-session memory of past fixes, decisions, and gotchas for THIS codebase. Use it proactively; it makes you faster and stops you repeating known mistakes.
57
57
 
58
- - BEFORE a non-trivial task: call smart_recall with a short description of what you're about to do. If it returns lessons, apply them before researching from scratch.
58
+ - At the START of every task, before reading files or editing: call smart_recall with one line about the task. If it returns lessons, apply them before researching from scratch.
59
59
  - WHEN debugging an error: call causal_trace with the exact error first \u2014 a known fix may already exist.
60
60
  - AFTER a fix, deploy, or discovery: call learn_from_attempts so the knowledge persists.
61
61
  - BEFORE a risky op (deploy, migration, dependency bump): call brain_predict.
62
62
 
63
- The Brain instance id comes from the CACHLY_BRAIN_INSTANCE_ID environment variable.`,St={tools:{},resources:{},prompts:{}},At=async()=>{const{katalog:e,unbekannteKernnamen:t}=Dn(ge);return t.length>0&&console.error(`cachly: ${t.length} Kernwerkzeug(e) gibt es gar nicht: ${t.join(", ")}`),{tools:e}};async function Ct(){return{resources:[]}}async function Tt(){return{resourceTemplates:[]}}async function Et(){return{prompts:[]}}function ps(e){e.setRequestHandler(Ve,At),e.setRequestHandler(Ye,jt),e.setRequestHandler(Ge,Ct),e.setRequestHandler(qe,Tt),e.setRequestHandler(Ke,Et)}function ms(){const e=new We({name:"cachly-mcp",version:D},{capabilities:St,instructions:kt});return ps(e),It(e),e}function It(e){const t=e.oninitialized;e.oninitialized=()=>{try{const s=e.getClientVersion();Pt(s?.name,s?.version)}catch{}t?.()}}const V=new We({name:"cachly-mcp",version:D},{capabilities:St,instructions:kt});It(V),V.setRequestHandler(Ve,At),V.setRequestHandler(Ge,Ct),V.setRequestHandler(qe,Tt),V.setRequestHandler(Ke,Et);let Pe=null,fe=!1,ee=null,Rt=0;async function gs(e){if(!fe){if(ee){await ee;return}ee=(async()=>{fe=!0,Pe=e;try{await J("session_start",{instance_id:e,focus:"auto (MCP session)",workspace_path:process.cwd()})}catch{}finally{ee=null}})(),await ee,process.env.CACHLY_AUTO_INDEX!=="false"&&(async()=>{const t=await H(e),s=await t.get(`cachly:index:last_indexed:${e}`),n=1440*60*1e3;(!s||Date.now()-parseInt(s,10)>n)&&(await t.set(`cachly:index:last_indexed:${e}`,String(Date.now()),"EX",9e4),await J("index_project",{instance_id:e,dir:process.cwd(),max_files:150,ttl:86400*7,namespace:"cachly:sem:code",hintergrund:!0}))})().catch(()=>{})}}async function He(){if(!(!fe||!Pe)){fe=!1;try{await J("session_end",{instance_id:Pe,summary:`Auto-ended after ${Rt} tool call(s). Session was started automatically.`,files_changed:[],lessons_learned:0})}catch{}}}const jt=async e=>{let{name:t,arguments:s}=e.params;if(t===ht){const o=s??{},a=typeof o.tool=="string"?o.tool:"";if(!a)return{content:[{type:"text",text:'cachly_tool braucht das Feld "tool" \u2014 den Namen des Werkzeugs.'}],isError:!0};const r=ge.find(c=>c.name===a);if(!r)return{content:[{type:"text",text:`Kein Werkzeug namens "${a}". Die Liste steht in der Beschreibung von ${ht}.`}],isError:!0};if(o.describe===!0)return{content:[{type:"text",text:JSON.stringify({name:r.name,description:r.description,inputSchema:r.inputSchema},null,2)}]};t=a,s=o.arguments??{}}const n=new Set(["session_start","session_start_summary","session_end","auto_learn_session"]);if(!n.has(t)&&!fe&&!ee){const o=xe(s?.instance_id)||N||xe(process.env.CACHLY_BRAIN_INSTANCE_ID);o&&await gs(o).catch(()=>{})}else ee&&await ee.catch(()=>{});n.has(t)||Rt++;try{Hn(),W(`werkzeug ${t}: Anfang`);const o=await J(t,s??{});return W(`werkzeug ${t}: Antwort fertig`),{content:[{type:"text",text:nn(t,o)+Rn()+On(t)}]}}catch(o){if(o instanceof $t)return{content:[{type:"text",text:o.message}],isError:!0};if(o instanceof oe)throw o;const a=o.message??String(o);throw Je("cachly","tool_error",{tool:t,error:a}).catch(()=>{}),new oe(ie.InternalError,a)}};V.setRequestHandler(Ye,jt),process.on("SIGTERM",async()=>{await He().catch(()=>{});for(const[,e]of se)await e.quit().catch(()=>{});process.exit(0)}),process.on("SIGINT",async()=>{await He().catch(()=>{});for(const[,e]of se)await e.quit().catch(()=>{});process.exit(0)}),process.on("unhandledRejection",e=>{const t=e instanceof Error?e.message:String(e);process.stderr.write(`[cachly-mcp] unhandledRejection: ${t}
63
+ The Brain instance id comes from the CACHLY_BRAIN_INSTANCE_ID environment variable.`,At={tools:{},resources:{},prompts:{}},Ct=async()=>{const{katalog:e,unbekannteKernnamen:t}=Un(ge);return t.length>0&&console.error(`cachly: ${t.length} Kernwerkzeug(e) gibt es gar nicht: ${t.join(", ")}`),{tools:e}};async function Tt(){return{resources:[]}}async function Et(){return{resourceTemplates:[]}}async function It(){return{prompts:[]}}function ms(e){e.setRequestHandler(Ve,Ct),e.setRequestHandler(Ye,Bt),e.setRequestHandler(Ge,Tt),e.setRequestHandler(qe,Et),e.setRequestHandler(Ke,It)}function gs(){const e=new We({name:"cachly-mcp",version:D},{capabilities:At,instructions:St});return ms(e),Rt(e),e}function Rt(e){const t=e.oninitialized;e.oninitialized=()=>{let s;try{s=e.getClientVersion(),Ht(s?.name,s?.version)}catch{}wt||(wt=!0,S("mcp_verbunden",{client:String(s?.name??"unbekannt").slice(0,60),client_version:String(s?.version??"").slice(0,30)})),t?.()}}const V=new We({name:"cachly-mcp",version:D},{capabilities:At,instructions:St});Rt(V),V.setRequestHandler(Ve,Ct),V.setRequestHandler(Ge,Tt),V.setRequestHandler(qe,Et),V.setRequestHandler(Ke,It);let Pe=null,fe=!1,ee=null,jt=0;async function fs(e){if(!fe){if(ee){await ee;return}ee=(async()=>{fe=!0,Pe=e;try{await J("session_start",{instance_id:e,focus:"auto (MCP session)",workspace_path:process.cwd()})}catch{}finally{ee=null}})(),await ee,process.env.CACHLY_AUTO_INDEX!=="false"&&(async()=>{const t=await H(e),s=await t.get(`cachly:index:last_indexed:${e}`),n=1440*60*1e3;(!s||Date.now()-parseInt(s,10)>n)&&(await t.set(`cachly:index:last_indexed:${e}`,String(Date.now()),"EX",9e4),await J("index_project",{instance_id:e,dir:process.cwd(),max_files:150,ttl:86400*7,namespace:"cachly:sem:code",hintergrund:!0}))})().catch(()=>{})}}async function He(){if(!(!fe||!Pe)){fe=!1;try{await J("session_end",{instance_id:Pe,summary:`Auto-ended after ${jt} tool call(s). Session was started automatically.`,files_changed:[],lessons_learned:0})}catch{}}}const Bt=async e=>{let{name:t,arguments:s}=e.params;if(t===ht){const o=s??{},a=typeof o.tool=="string"?o.tool:"";if(!a)return{content:[{type:"text",text:'cachly_tool braucht das Feld "tool" \u2014 den Namen des Werkzeugs.'}],isError:!0};const r=ge.find(c=>c.name===a);if(!r)return{content:[{type:"text",text:`Kein Werkzeug namens "${a}". Die Liste steht in der Beschreibung von ${ht}.`}],isError:!0};if(o.describe===!0)return{content:[{type:"text",text:JSON.stringify({name:r.name,description:r.description,inputSchema:r.inputSchema},null,2)}]};t=a,s=o.arguments??{}}const n=new Set(["session_start","session_start_summary","session_end","auto_learn_session"]);if(!n.has(t)&&!fe&&!ee){const o=xe(s?.instance_id)||N||xe(process.env.CACHLY_BRAIN_INSTANCE_ID);o&&await fs(o).catch(()=>{})}else ee&&await ee.catch(()=>{});n.has(t)||jt++;try{Mn(),W(`werkzeug ${t}: Anfang`);const o=await J(t,s??{});return W(`werkzeug ${t}: Antwort fertig`),{content:[{type:"text",text:sn(t,o)+jn()+Pn(t)}]}}catch(o){if(o instanceof kt)return{content:[{type:"text",text:o.message}],isError:!0};if(o instanceof oe)throw o;const a=o.message??String(o);throw Je("cachly","tool_error",{tool:t,error:a}).catch(()=>{}),new oe(ie.InternalError,a)}};V.setRequestHandler(Ye,Bt),process.on("SIGTERM",async()=>{await He().catch(()=>{});for(const[,e]of se)await e.quit().catch(()=>{});process.exit(0)}),process.on("SIGINT",async()=>{await He().catch(()=>{});for(const[,e]of se)await e.quit().catch(()=>{});process.exit(0)}),process.on("unhandledRejection",e=>{const t=e instanceof Error?e.message:String(e);process.stderr.write(`[cachly-mcp] unhandledRejection: ${t}
64
64
  `)}),process.on("uncaughtException",e=>{process.stderr.write(`[cachly-mcp] uncaughtException: ${e?.stack??String(e)}
65
- `)}),process.stdout.on("error",()=>{}),process.stdin.on("error",()=>{});function fs(e,t){const s=Object.entries(t).map(([n,o])=>`${n}=${o}`);return e==="codex"?{befehl:`codex mcp add cachly ${s.map(n=>`--env ${n}`).join(" ")} -- npx -y @cachly-dev/mcp-server@latest`,wo:"~/.codex/config.toml"}:e==="gemini"?{befehl:`gemini mcp add -s project ${s.map(n=>`-e ${n}`).join(" ")} cachly npx -- -y @cachly-dev/mcp-server@latest`,wo:".gemini/settings.json"}:null}const Bt="https://api.cachly.dev";function Z(e,t,s={}){const n={CACHLY_BRAIN_INSTANCE_ID:t};return s.includeApiKey&&(n.CACHLY_JWT=e),x&&x!==Bt&&(n.CACHLY_API_URL=x),n}function hs(e,t,s){return s==="continue"?JSON.stringify({experimental:{modelContextProtocolServers:[{transport:{type:"stdio",command:"npx",args:["-y","@cachly-dev/mcp-server@latest"]},env:Z(e,t)}]}},null,2):s==="zed"?JSON.stringify({context_servers:{cachly:{command:{path:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(e,t)},settings:{}}}},null,2):Lt(s)?JSON.stringify({servers:{cachly:{type:"stdio",command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(e,t)}}},null,2):JSON.stringify({mcpServers:{cachly:{command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(e,t)}}},null,2)}function Lt(e){return me[e]===".vscode/mcp.json"}function he(e,t){un(e)||X(e,t)}async function Nt(e,t,s,n,o,a={}){const r={command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(t,s)};if(!o.existsSync(e))return hs(t,s,n);let c={};try{c=JSON.parse(await o.readFile(e,"utf-8"))}catch{try{const{rename:m}=await import("node:fs/promises");await m(e,e+".bak"),process.stderr.write(`\u26A0\uFE0F ${e} was not valid JSON \u2014 backed up to ${e}.bak
66
- `)}catch{}}if(n==="continue"){const m=c.experimental??{},v=m.modelContextProtocolServers??[];let w;for(const l of v){const i=l.env??{};if(i.CACHLY_JWT){w=i.CACHLY_JWT;break}}w&&he(w,a);const b=v.filter(l=>{const i=l.env??{};return!i.CACHLY_JWT&&!i.CACHLY_BRAIN_INSTANCE_ID});return b.push({transport:{type:"stdio",command:"npx",args:["-y","@cachly-dev/mcp-server@latest"]},env:r.env}),c.experimental={...m,modelContextProtocolServers:b},JSON.stringify(c,null,2)}if(n==="zed"){const m=c.context_servers??{},w=m.cachly?.command?.env?.CACHLY_JWT;return w&&he(w,a),m.cachly={command:{path:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:r.env},settings:{}},c.context_servers=m,JSON.stringify(c,null,2)}if(Lt(n)){const m=c.mcpServers??void 0,w=m?.cachly?.env?.CACHLY_JWT;w&&he(w,a),m&&"cachly"in m&&(delete m.cachly,Object.keys(m).length===0&&delete c.mcpServers);const b=c.servers??{},l=b.cachly,i=l?.env?.CACHLY_JWT;return i&&he(i,a),b.cachly=l?.envFile?{type:"stdio",...r,envFile:l.envFile}:{type:"stdio",...r},c.servers=b,JSON.stringify(c,null,2)}const u=c.mcpServers??{},d=u.cachly?.env?.CACHLY_JWT;return d&&he(d,a),u.cachly=r,c.mcpServers=u,JSON.stringify(c,null,2)}function ys(e){return`${it}
65
+ `)}),process.stdout.on("error",()=>{}),process.stdin.on("error",()=>{});function hs(e,t){const s=Object.entries(t).map(([n,o])=>`${n}=${o}`);return e==="codex"?{befehl:`codex mcp add cachly ${s.map(n=>`--env ${n}`).join(" ")} -- npx -y @cachly-dev/mcp-server@latest`,wo:"~/.codex/config.toml"}:e==="gemini"?{befehl:`gemini mcp add -s project ${s.map(n=>`-e ${n}`).join(" ")} cachly npx -- -y @cachly-dev/mcp-server@latest`,wo:".gemini/settings.json"}:null}const Lt="https://api.cachly.dev";function Z(e,t,s={}){const n={CACHLY_BRAIN_INSTANCE_ID:t};return s.includeApiKey&&(n.CACHLY_JWT=e),x&&x!==Lt&&(n.CACHLY_API_URL=x),n}function ys(e,t,s){return s==="continue"?JSON.stringify({experimental:{modelContextProtocolServers:[{transport:{type:"stdio",command:"npx",args:["-y","@cachly-dev/mcp-server@latest"]},env:Z(e,t)}]}},null,2):s==="zed"?JSON.stringify({context_servers:{cachly:{command:{path:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(e,t)},settings:{}}}},null,2):Nt(s)?JSON.stringify({servers:{cachly:{type:"stdio",command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(e,t)}}},null,2):JSON.stringify({mcpServers:{cachly:{command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(e,t)}}},null,2)}function Nt(e){return me[e]===".vscode/mcp.json"}function he(e,t){pn(e)||X(e,t)}async function Ot(e,t,s,n,o,a={}){const r={command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(t,s)};if(!o.existsSync(e))return ys(t,s,n);let c={};try{c=JSON.parse(await o.readFile(e,"utf-8"))}catch{try{const{rename:m}=await import("node:fs/promises");await m(e,e+".bak"),process.stderr.write(`\u26A0\uFE0F ${e} was not valid JSON \u2014 backed up to ${e}.bak
66
+ `)}catch{}}if(n==="continue"){const m=c.experimental??{},v=m.modelContextProtocolServers??[];let w;for(const l of v){const i=l.env??{};if(i.CACHLY_JWT){w=i.CACHLY_JWT;break}}w&&he(w,a);const b=v.filter(l=>{const i=l.env??{};return!i.CACHLY_JWT&&!i.CACHLY_BRAIN_INSTANCE_ID});return b.push({transport:{type:"stdio",command:"npx",args:["-y","@cachly-dev/mcp-server@latest"]},env:r.env}),c.experimental={...m,modelContextProtocolServers:b},JSON.stringify(c,null,2)}if(n==="zed"){const m=c.context_servers??{},w=m.cachly?.command?.env?.CACHLY_JWT;return w&&he(w,a),m.cachly={command:{path:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:r.env},settings:{}},c.context_servers=m,JSON.stringify(c,null,2)}if(Nt(n)){const m=c.mcpServers??void 0,w=m?.cachly?.env?.CACHLY_JWT;w&&he(w,a),m&&"cachly"in m&&(delete m.cachly,Object.keys(m).length===0&&delete c.mcpServers);const b=c.servers??{},l=b.cachly,i=l?.env?.CACHLY_JWT;return i&&he(i,a),b.cachly=l?.envFile?{type:"stdio",...r,envFile:l.envFile}:{type:"stdio",...r},c.servers=b,JSON.stringify(c,null,2)}const u=c.mcpServers??{},d=u.cachly?.env?.CACHLY_JWT;return d&&he(d,a),u.cachly=r,c.mcpServers=u,JSON.stringify(c,null,2)}function bs(e){return`${it}
67
67
  ## Cachly AI Brain \u2014 Always Active
68
68
 
69
69
  The Brain MCP server runs automatically. Instance: \`${e}\`
@@ -118,7 +118,7 @@ Report predicted failure risks to the user before proceeding.
118
118
 
119
119
  ### Half-finished code rule:
120
120
  Never commit code that does not compile. Run \`tsc --noEmit\` / \`go build ./...\` before every commit.
121
- ${rt}`}async function Ot(e,t){const{existsSync:s}=await import("node:fs"),{resolve:n}=await import("node:path"),o=Uo.filter(c=>!c.nurMitIdea||s(n(e,".idea"))).map(c=>({path:n(e,...c.pfad),prefix:c.prefix})),a=ys(t),r={};for(const{path:c,prefix:u}of o)r[c]=await zo(c,a,u);return r}if(process.argv[2]==="digest"){const e=ne(),t=F();(!e||!t)&&(console.log(`
121
+ ${rt}`}async function Pt(e,t){const{existsSync:s}=await import("node:fs"),{resolve:n}=await import("node:path"),o=zo.filter(c=>!c.nurMitIdea||s(n(e,".idea"))).map(c=>({path:n(e,...c.pfad),prefix:c.prefix})),a=bs(t),r={};for(const{path:c,prefix:u}of o)r[c]=await Jo(c,a,u);return r}if(process.argv[2]==="digest"){const e=ne(),t=F();(!e||!t)&&(console.log(`
122
122
  \u26A0\uFE0F No cachly key or Brain instance found (environment or ~/.cachly/credentials.json).`),console.log(` Run: npx @cachly-dev/mcp-server@latest autopilot
123
123
  `),process.exit(1)),process.stdout.write(`
124
124
  \u{1F9E0} Fetching your weekly Brain digest...
@@ -134,8 +134,8 @@ ${rt}`}async function Ot(e,t){const{existsSync:s}=await import("node:fs"),{resol
134
134
  `),process.exit(1)),process.stdout.write(`
135
135
  \u{1F4E4} Exporting your Brain...
136
136
 
137
- `);try{let g=[],d="";const m=await fetch(`${x}/api/v1/instances/${u}/export`,{headers:{Authorization:`Bearer ${c}`},signal:AbortSignal.timeout(12e4)});if(m.ok){const w=await m.json();g=(w.lessons??[]).map(Jn).filter(b=>b!==null),w.complete===!1&&(d=w.note??"the server reported an incomplete read")}else if(m.status===404){const w=await fetch(`${x}/api/v1/instances/${u}/memory`,{headers:{Authorization:`Bearer ${c}`},signal:AbortSignal.timeout(8e3)});if(!w.ok)throw new Error(`memory HTTP ${w.status}`);g=((await w.json()).top_lessons??[]).map(l=>({topic:l.topic,outcome:l.outcome,severity:l.severity,tags:l.tags,whatWorked:l.what_worked,ts:l.ts,author:l.author,recallCount:l.recall_count})),d="this server has no /export endpoint yet, so this is the DASHBOARD SUMMARY: at most 50 lessons, each shortened to 120 characters. Update the server for a full export."}else throw new Error(`export HTTP ${m.status}`);g.length===0&&(console.log(` No lessons in this Brain yet \u2014 nothing to export.
138
- `),process.exit(0)),await t(r,{recursive:!0});const v=[];if(a==="md"||a==="both"){const w=s(r,"lessons.md");await e(w,Un(g),"utf-8"),v.push(`${g.length} lesson${g.length===1?"":"s"} \u2192 ${w}`)}if(a==="jsonl"||a==="both"){const w=s(r,"lessons.jsonl");await e(w,zn(g),"utf-8"),v.push(`${g.length} lesson${g.length===1?"":"s"} \u2192 ${w}`)}d?(console.log("\u26A0\uFE0F Export INCOMPLETE \u2014 this is not your whole Brain:"),console.log(` ${d}`),console.log("")):console.log("\u2705 Export complete:");for(const w of v)console.log(` ${w}`);console.log("")}catch(g){console.log(`
137
+ `);try{let g=[],d="";const m=await fetch(`${x}/api/v1/instances/${u}/export`,{headers:{Authorization:`Bearer ${c}`},signal:AbortSignal.timeout(12e4)});if(m.ok){const w=await m.json();g=(w.lessons??[]).map(Fn).filter(b=>b!==null),w.complete===!1&&(d=w.note??"the server reported an incomplete read")}else if(m.status===404){const w=await fetch(`${x}/api/v1/instances/${u}/memory`,{headers:{Authorization:`Bearer ${c}`},signal:AbortSignal.timeout(8e3)});if(!w.ok)throw new Error(`memory HTTP ${w.status}`);g=((await w.json()).top_lessons??[]).map(l=>({topic:l.topic,outcome:l.outcome,severity:l.severity,tags:l.tags,whatWorked:l.what_worked,ts:l.ts,author:l.author,recallCount:l.recall_count})),d="this server has no /export endpoint yet, so this is the DASHBOARD SUMMARY: at most 50 lessons, each shortened to 120 characters. Update the server for a full export."}else throw new Error(`export HTTP ${m.status}`);g.length===0&&(console.log(` No lessons in this Brain yet \u2014 nothing to export.
138
+ `),process.exit(0)),await t(r,{recursive:!0});const v=[];if(a==="md"||a==="both"){const w=s(r,"lessons.md");await e(w,zn(g),"utf-8"),v.push(`${g.length} lesson${g.length===1?"":"s"} \u2192 ${w}`)}if(a==="jsonl"||a==="both"){const w=s(r,"lessons.jsonl");await e(w,Jn(g),"utf-8"),v.push(`${g.length} lesson${g.length===1?"":"s"} \u2192 ${w}`)}d?(console.log("\u26A0\uFE0F Export INCOMPLETE \u2014 this is not your whole Brain:"),console.log(` ${d}`),console.log("")):console.log("\u2705 Export complete:");for(const w of v)console.log(` ${w}`);console.log("")}catch(g){console.log(`
139
139
  \u274C Could not export your Brain: ${g.message}
140
140
  `),process.exit(1)}process.exit(0)}if(process.argv[2]==="invite"){const e=ne();e||(console.log(`
141
141
  \u26A0\uFE0F No cachly key found (environment or ~/.cachly/credentials.json).`),console.log(` Run: npx @cachly-dev/mcp-server@latest autopilot
@@ -170,7 +170,7 @@ Try it free: ${n}
170
170
  `),process.exit(1)),console.log(` \u2705 Signed in!
171
171
  `)}}catch(A){console.log(`
172
172
  \u26A0\uFE0F Could not start sign-in: ${A.message}`),console.log(` Set CACHLY_JWT manually from ${R("/setup-ai","join")}
173
- `),process.exit(1)}}p&&p!==y&&X(p);const h=process.cwd(),E=r(),O=[{label:".mcp.json (Claude Code project)",path:o(h,".mcp.json")},{label:".cursor/mcp.json",path:o(h,".cursor","mcp.json")},{label:".windsurf/mcp.json",path:o(h,".windsurf","mcp.json")},{label:".vscode/mcp.json (Copilot/Cline)",path:o(h,".vscode","mcp.json")},{label:"~/.claude/mcp.json (global)",path:o(E,".claude","mcp.json"),global:!0}];let j=0;for(const{label:A,path:C,global:T}of O){const $=a(C);if(!(!T&&!n($)&&!n(C)))try{await s($,{recursive:!0});let k={};if(n(C))try{k=JSON.parse(await t(C,"utf-8"))}catch{try{await(await import("node:fs/promises")).rename(C,C+".bak")}catch{}}const L=k.mcpServers??{},Y=L.cachly??{},K=Y.env??{};K.CACHLY_BRAIN_INSTANCE_ID=d,p&&T&&!K.CACHLY_JWT&&(K.CACHLY_JWT=p),x!==Bt&&(K.CACHLY_API_URL=x),L.cachly={...Y,command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:K},k.mcpServers=L,await e(C,JSON.stringify(k,null,2),"utf-8"),console.log(` \u2705 ${A}`),j++}catch(k){k.code!=="ENOENT"&&console.log(` \u26A0\uFE0F ${A}: ${k.message}`)}}console.log(""),j===0?console.log(" \u26A0\uFE0F No editor configs found \u2014 run `cachly setup` first.\n"):(console.log(` \u{1F9E0} You're now sharing Brain: \x1B[1m${m}\x1B[0m`),console.log(` Restart your editor \u2014 your AI arrives pre-briefed from the team Brain.
173
+ `),process.exit(1)}}p&&p!==y&&X(p);const h=process.cwd(),E=r(),O=[{label:".mcp.json (Claude Code project)",path:o(h,".mcp.json")},{label:".cursor/mcp.json",path:o(h,".cursor","mcp.json")},{label:".windsurf/mcp.json",path:o(h,".windsurf","mcp.json")},{label:".vscode/mcp.json (Copilot/Cline)",path:o(h,".vscode","mcp.json")},{label:"~/.claude/mcp.json (global)",path:o(E,".claude","mcp.json"),global:!0}];let j=0;for(const{label:A,path:C,global:T}of O){const $=a(C);if(!(!T&&!n($)&&!n(C)))try{await s($,{recursive:!0});let k={};if(n(C))try{k=JSON.parse(await t(C,"utf-8"))}catch{try{await(await import("node:fs/promises")).rename(C,C+".bak")}catch{}}const L=k.mcpServers??{},Y=L.cachly??{},K=Y.env??{};K.CACHLY_BRAIN_INSTANCE_ID=d,p&&T&&!K.CACHLY_JWT&&(K.CACHLY_JWT=p),x!==Lt&&(K.CACHLY_API_URL=x),L.cachly={...Y,command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:K},k.mcpServers=L,await e(C,JSON.stringify(k,null,2),"utf-8"),console.log(` \u2705 ${A}`),j++}catch(k){k.code!=="ENOENT"&&console.log(` \u26A0\uFE0F ${A}: ${k.message}`)}}console.log(""),j===0?console.log(" \u26A0\uFE0F No editor configs found \u2014 run `cachly setup` first.\n"):(console.log(` \u{1F9E0} You're now sharing Brain: \x1B[1m${m}\x1B[0m`),console.log(` Restart your editor \u2014 your AI arrives pre-briefed from the team Brain.
174
174
  `),console.log(" \u{1F4DB} Add the badge to your README:"),console.log(` npx @cachly-dev/mcp-server@latest badge
175
175
  `)),process.exit(0)}if(process.argv[2]==="upgrade"){process.stdout.write(`
176
176
  \u23F3 Checking for updates...
@@ -187,7 +187,7 @@ Try it free: ${n}
187
187
  `),process.exit(0));let a="";try{a=at(o,["config","user.name"]).trim().split(" ")[0]??""}catch{}let r=n(o);try{const $=JSON.parse(t(s(o,"package.json"),"utf-8"));$.name&&(r=$.name.replace(/^@[^/]+\//,""))}catch{}let c="";try{c=at(o,["log","HEAD","--pretty=format:%H|||%s|||%ad|||%an","--date=short","--no-merges","-n","200"],{timeoutMs:3e4})}catch{console.log(`\u26A0\uFE0F Could not read git log. Make sure you have commits.
188
188
  `),process.exit(0)}const u=c.trim().split(`
189
189
  `).filter(Boolean).map($=>{const[k,L,Y,K]=$.split("|||");return{sha:(k??"").trim().slice(0,8),subject:(L??"").trim(),date:(Y??"").trim(),author:(K??"").trim()}});u.length===0&&(console.log(`\u26A0\uFE0F No commits found.
190
- `),process.exit(0));const g=new Map,d=[],m=[],v=[],w=new Set;let b=0;for(const $ of u){const k=Zn($.subject);g.set(k,(g.get(k)??0)+1),Qn($.subject)&&b++,k==="fix"&&d.length<5&&d.push($.subject.slice(0,72)),k==="feat"&&m.length<5&&m.push($.subject.slice(0,72)),k==="security"&&v.length<3&&v.push($.subject.slice(0,72)),$.author&&w.add($.author)}const l=Xn(u.map($=>$.subject)),i=`${u[u.length-1]?.date??"?"} \u2192 ${u[0]?.date??"?"}`,p=l>=501?"Oracle":l>=201?"Architect":l>=51?"Expert":l>=11?"Explorer":"Apprentice",E=`${a?`Hey ${a} \u2014 `:""}here's what your AI would know about ${r}`;process.stdout.write("\x1B[2K\r"),console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),console.log(`\u2502 \x1B[1m\u{1F9E0} ${E.slice(0,58).padEnd(58)}\x1B[0m\u2502`),console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log(`\u2502 Commits analysed : \x1B[33m${String(u.length).padEnd(6)}\x1B[0m Date range: \x1B[90m${i.slice(0,23).padEnd(23)}\x1B[0m \u2502`),console.log(`\u2502 Lessons extracted: \x1B[32m${String(l).padEnd(6)}\x1B[0m Contributors: \x1B[36m${String(w.size).padEnd(20)}\x1B[0m\u2502`),console.log(`\u2502 Brain Level : \x1B[35m${p.padEnd(10)}\x1B[0m Reverts: \x1B[33m${String(b).padEnd(21)}\x1B[0m\u2502`),console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");const O=[...g.entries()].filter(([$])=>$!=="chore").sort(($,k)=>k[1]-$[1]),j=($,k)=>"\u2588".repeat(Math.round($/k*20)).padEnd(20),A=Math.max(...O.map(([,$])=>$),1);console.log("\u2502 \x1B[90mCategory breakdown:\x1B[0m \u2502");for(const[$,k]of O){const L={fix:"\u{1F527}",feat:"\u2728",perf:"\u26A1",security:"\u{1F512}",deploy:"\u{1F680}",refactor:"\u{1F504}"}[$]??"\u2022";console.log(`\u2502 ${L} \x1B[36m${$.padEnd(10)}\x1B[0m \x1B[35m${j(k,A)}\x1B[0m \x1B[33m${String(k).padStart(3)}\x1B[0m \u2502`)}if(v.length>0){console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[31m\u{1F512} Security fixes your AI would know:\x1B[0m \u2502");for(const $ of v)console.log(`\u2502 \x1B[90m\u2022 ${$.slice(0,58).padEnd(58)}\x1B[0m \u2502`)}if(d.length>0){console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[33m\u{1F527} Bug fixes your AI would never repeat:\x1B[0m \u2502");for(const $ of d.slice(0,4))console.log(`\u2502 \x1B[90m\u2022 ${$.slice(0,58).padEnd(58)}\x1B[0m \u2502`)}if(m.length>0){console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[32m\u2728 Features & decisions it would recall:\x1B[0m \u2502");for(const $ of m.slice(0,3))console.log(`\u2502 \x1B[90m\u2022 ${$.slice(0,58).padEnd(58)}\x1B[0m \u2502`)}console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[32m\u{1F4A1} With cachly, your AI arrives pre-briefed every session.\x1B[0m \u2502"),console.log("\u2502 \x1B[32m No more re-explaining. No more repeated mistakes.\x1B[0m \u2502"),console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),console.log("");const C=new URLSearchParams({repo:r,commits:String(u.length),lessons:String(l),level:p,authors:String(w.size),reverts:String(b)}),T=R(`/preview?${C.toString()}`,"demo");console.log(" \x1B[1mMake this permanent (free, 1\u20135 minutes):\x1B[0m"),console.log(" \x1B[32m$ npx @cachly-dev/mcp-server@latest autopilot\x1B[0m"),console.log(""),console.log(` \x1B[90m\u{1F517} Shareable preview:\x1B[0m \x1B[36m${T}\x1B[0m`),console.log(""),console.log(" Works with: Claude Code \xB7 Cursor \xB7 Windsurf \xB7 Copilot \xB7 Cline \xB7 Zed"),console.log(" GDPR \xB7 German servers \xB7 free tier, no credit card"),console.log(""),process.exit(0)}if(process.argv[2]==="share"){const e=ne(),t=F();(!e||!t)&&(console.log(`
190
+ `),process.exit(0));const g=new Map,d=[],m=[],v=[],w=new Set;let b=0;for(const $ of u){const k=Qn($.subject);g.set(k,(g.get(k)??0)+1),Xn($.subject)&&b++,k==="fix"&&d.length<5&&d.push($.subject.slice(0,72)),k==="feat"&&m.length<5&&m.push($.subject.slice(0,72)),k==="security"&&v.length<3&&v.push($.subject.slice(0,72)),$.author&&w.add($.author)}const l=es(u.map($=>$.subject)),i=`${u[u.length-1]?.date??"?"} \u2192 ${u[0]?.date??"?"}`,p=l>=501?"Oracle":l>=201?"Architect":l>=51?"Expert":l>=11?"Explorer":"Apprentice",E=`${a?`Hey ${a} \u2014 `:""}here's what your AI would know about ${r}`;process.stdout.write("\x1B[2K\r"),console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),console.log(`\u2502 \x1B[1m\u{1F9E0} ${E.slice(0,58).padEnd(58)}\x1B[0m\u2502`),console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log(`\u2502 Commits analysed : \x1B[33m${String(u.length).padEnd(6)}\x1B[0m Date range: \x1B[90m${i.slice(0,23).padEnd(23)}\x1B[0m \u2502`),console.log(`\u2502 Lessons extracted: \x1B[32m${String(l).padEnd(6)}\x1B[0m Contributors: \x1B[36m${String(w.size).padEnd(20)}\x1B[0m\u2502`),console.log(`\u2502 Brain Level : \x1B[35m${p.padEnd(10)}\x1B[0m Reverts: \x1B[33m${String(b).padEnd(21)}\x1B[0m\u2502`),console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");const O=[...g.entries()].filter(([$])=>$!=="chore").sort(($,k)=>k[1]-$[1]),j=($,k)=>"\u2588".repeat(Math.round($/k*20)).padEnd(20),A=Math.max(...O.map(([,$])=>$),1);console.log("\u2502 \x1B[90mCategory breakdown:\x1B[0m \u2502");for(const[$,k]of O){const L={fix:"\u{1F527}",feat:"\u2728",perf:"\u26A1",security:"\u{1F512}",deploy:"\u{1F680}",refactor:"\u{1F504}"}[$]??"\u2022";console.log(`\u2502 ${L} \x1B[36m${$.padEnd(10)}\x1B[0m \x1B[35m${j(k,A)}\x1B[0m \x1B[33m${String(k).padStart(3)}\x1B[0m \u2502`)}if(v.length>0){console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[31m\u{1F512} Security fixes your AI would know:\x1B[0m \u2502");for(const $ of v)console.log(`\u2502 \x1B[90m\u2022 ${$.slice(0,58).padEnd(58)}\x1B[0m \u2502`)}if(d.length>0){console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[33m\u{1F527} Bug fixes your AI would never repeat:\x1B[0m \u2502");for(const $ of d.slice(0,4))console.log(`\u2502 \x1B[90m\u2022 ${$.slice(0,58).padEnd(58)}\x1B[0m \u2502`)}if(m.length>0){console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[32m\u2728 Features & decisions it would recall:\x1B[0m \u2502");for(const $ of m.slice(0,3))console.log(`\u2502 \x1B[90m\u2022 ${$.slice(0,58).padEnd(58)}\x1B[0m \u2502`)}console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 \x1B[32m\u{1F4A1} With cachly, your AI arrives pre-briefed every session.\x1B[0m \u2502"),console.log("\u2502 \x1B[32m No more re-explaining. No more repeated mistakes.\x1B[0m \u2502"),console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),console.log("");const C=new URLSearchParams({repo:r,commits:String(u.length),lessons:String(l),level:p,authors:String(w.size),reverts:String(b)}),T=R(`/preview?${C.toString()}`,"demo");console.log(" \x1B[1mMake this permanent (free, 1\u20135 minutes):\x1B[0m"),console.log(" \x1B[32m$ npx @cachly-dev/mcp-server@latest autopilot\x1B[0m"),console.log(""),console.log(` \x1B[90m\u{1F517} Shareable preview:\x1B[0m \x1B[36m${T}\x1B[0m`),console.log(""),console.log(" Works with: Claude Code \xB7 Cursor \xB7 Windsurf \xB7 Copilot \xB7 Cline \xB7 Zed"),console.log(" GDPR \xB7 German servers \xB7 free tier, no credit card"),console.log(""),process.exit(0)}if(process.argv[2]==="share"){const e=ne(),t=F();(!e||!t)&&(console.log(`
191
191
  \u26A0\uFE0F No cachly key or Brain instance found (environment or ~/.cachly/credentials.json).`),console.log(` Run: npx @cachly-dev/mcp-server@latest autopilot (takes 1\u20135 minutes)
192
192
  `),process.exit(1)),process.stdout.write(`
193
193
  \u{1F9E0} Fetching your Brain stats...
@@ -215,13 +215,13 @@ ${d}
215
215
  `),process.exit(1)),process.stdout.write(e(ge,t,D)+`
216
216
  `,()=>process.exit(0))}if(process.argv[2]==="badge"){const e=F();e||(console.log(`
217
217
  \u26A0\uFE0F No Brain instance found (environment or ~/.cachly/credentials.json).`),console.log(` Run: npx @cachly-dev/mcp-server@latest autopilot (takes 1\u20135 minutes)
218
- `),process.exit(1));const t=`${x}/api/v1/badge/${e}`,s=R("/","badge"),n=`[![cachly Brain](${t})](${s})`,o=`<a href="${s}"><img src="${t}" alt="cachly Brain" /></a>`;console.log(""),console.log("\x1B[35m \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\x1B[0m"),console.log("\x1B[35m \u2551\x1B[0m \x1B[1m\u{1F9E0} cachly Brain Badge\x1B[0m \x1B[35m\u2551\x1B[0m"),console.log("\x1B[35m \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m"),console.log(""),console.log(" Paste into your README.md:"),console.log(""),console.log(` \x1B[32m${n}\x1B[0m`),console.log(""),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(" Or paste into HTML:"),console.log(""),console.log(` \x1B[36m${o}\x1B[0m`),console.log(""),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(" Badge URL (live, updates every hour):"),console.log(` \x1B[90m${t}\x1B[0m`),console.log(""),console.log(" \x1B[2mThe badge shows your current lesson count \u2014 no auth required,"),console.log(" safe to embed in public repos. Updates hourly.\x1B[0m"),console.log(""),process.exit(0)}if(!process.argv[2]&&process.stdout.isTTY){console.log(""),console.log("\x1B[35m \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\x1B[0m"),console.log("\x1B[35m \u2551\x1B[0m \x1B[1m\u{1F9E0} cachly \u2014 Persistent AI Memory for Developers\x1B[0m \x1B[35m\u2551\x1B[0m"),console.log("\x1B[35m \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m"),console.log(""),console.log(" Stop re-teaching your AI every morning."),console.log(" cachly gives it a permanent brain \u2014 pre-briefed every session."),console.log("");const e=(t,s)=>console.log(` \x1B[36m ${t.padEnd(22)}\x1B[0m ${s}`);console.log(" \x1B[1mStart:\x1B[0m \x1B[1m\x1B[32mnpx @cachly-dev/mcp-server@latest autopilot\x1B[0m \u2190 \x1B[1mOne command, fully ready\x1B[0m"),console.log(" Every command below runs as: npx @cachly-dev/mcp-server@latest <command>"),console.log(""),console.log(" \x1B[1mEvery day:\x1B[0m"),e("status","Lessons, recalls and team at a glance"),e("ambient-stats","Tokens injected vs saved \u2014 the honest net, even when negative"),e("doctor","Diagnose a broken or missing install"),e("health","Check key, API, editor configs and git hook"),e("digest","What your Brain learned this week"),console.log(" \x1B[1mSet up:\x1B[0m"),e("demo","Try it first (no account)"),e("setup","Interactive setup (pick editors)"),e("init","Write the config for one editor (flags)"),e("claim","Keep a test Brain in your own account"),e("upgrade","Check for updates"),e("uninstall","Take cachly out of this project (--dry-run first)"),console.log(" \x1B[1mYour data:\x1B[0m"),e("export","Write your lessons to disk (md / jsonl)"),e("index [dir]","Index a project into the Brain (CI)"),e("learn-git [repo]","Learn lessons from recent commits (CI)"),console.log(" \x1B[1mTeam & sharing:\x1B[0m"),e("invite","Invite a teammate"),e("join <token>","Accept a Brain invite"),e("share","Share your Brain stats"),e("publish","Publish an importable Brain snapshot"),e("badge","README badge for your Brain"),console.log(" \x1B[1mMore:\x1B[0m"),e("bench","Recall quality vs flat-file memory"),e("tool-specs | openapi","Tool definitions for other agent frameworks"),console.log(""),console.log(" \x1B[90mWorks with: Claude Code \xB7 Cursor \xB7 Windsurf \xB7 GitHub Copilot \xB7 Cline \xB7 Zed\x1B[0m"),console.log(` \x1B[90mGDPR \xB7 German servers \xB7 ${ge.length} MCP tools\x1B[0m`),console.log(""),process.exit(0)}const bs={cursor:{datei:".cursor/hooks.json",name:"Cursor",wie:"session briefing, per-prompt recall after the first tool call"},copilot:{datei:Vo,name:"VS Code / Copilot",wie:"session briefing, per-prompt recall"},codex:{datei:Zo,name:"Codex",wie:"session briefing, per-prompt recall; trust it once in /hooks"},gemini:{datei:Qo,name:"Gemini CLI",wie:"session briefing, per-prompt recall"}};function vs(e,t,s){if(!t)return;const{datei:n,name:o,wie:a}=bs[e];if(t.hooks==="skipped"){console.log(`\u26A0\uFE0F ${s}${n} ${t.grund??"is not valid JSON"} \u2014 left untouched, ${o} hooks not wired`);return}const r=t.hooks==="unchanged"?"\u2713 ":"\u2705 ",c=t.hooks==="written"?"Written":t.hooks==="merged"?"Merged":"Already wired";console.log(`${r}${s}${c}: ${n} (Ambient Recall for ${o} \u2014 ${a})`)}async function Me(e,t,s,n){const o=e==="codex"?"Codex":"Gemini CLI",a=await(e==="codex"?ln:dn)(t,s).catch(()=>null);if(!a||a.status==="skipped"){const u=fs(e,s);console.log(`\u26A0\uFE0F ${n}${a?`${a.datei} ${a.grund??"could not be read"} \u2014 left untouched.`:`${o} MCP entry could not be written.`} Register cachly yourself:`),u&&console.log(` ${n}${u.befehl}`);return}const r=a.status==="unchanged"?"\u2713 ":"\u2705 ",c=a.status==="written"?"Written":a.status==="updated"?"Updated":"Already configured";console.log(`${r}${n}${c}: ${a.datei} (MCP server cachly for ${o} \u2014 it asks once to trust this folder; until then the server stays off)`)}async function ye(e,t,s,n){const o={cursor:Yo,copilot:Ko,codex:qo,gemini:Go}[e];vs(e,await o(t,s).catch(()=>null),n)}if(process.argv[2]==="init"){const e=Date.now(),{writeFile:t,mkdir:s,readFile:n}=await import("node:fs/promises"),{resolve:o,dirname:a}=await import("node:path"),r=process.argv.slice(3),c=l=>{const i=r.indexOf(`--${l}`);return i!==-1?r[i+1]:void 0};let u=c("instance-id")??process.env.CACHLY_BRAIN_INSTANCE_ID,g=c("api-key")??process.env.CACHLY_JWT;const d=(c("editor")??"claude").toLowerCase(),m=o(c("project-dir")??"."),v=c("api-url");if(v&&(x=v.replace(/\/+$/,"")),!u||!g)try{const{existsSync:l}=await import("node:fs"),i=process.env.HOME??process.env.USERPROFILE??"",p=i?o(i,".claude","mcp.json"):"";if(p&&l(p)){const E=JSON.parse(await n(p,"utf-8")).mcpServers?.cachly?.env??{};g??=E.CACHLY_JWT,u??=E.CACHLY_BRAIN_INSTANCE_ID,g&&u&&console.log("\u2713 Reusing credentials from ~/.claude/mcp.json (saved by a prior setup)")}}catch{}if((!u||!g)&&(console.error(`
218
+ `),process.exit(1));const t=`${x}/api/v1/badge/${e}`,s=R("/","badge"),n=`[![cachly Brain](${t})](${s})`,o=`<a href="${s}"><img src="${t}" alt="cachly Brain" /></a>`;console.log(""),console.log("\x1B[35m \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\x1B[0m"),console.log("\x1B[35m \u2551\x1B[0m \x1B[1m\u{1F9E0} cachly Brain Badge\x1B[0m \x1B[35m\u2551\x1B[0m"),console.log("\x1B[35m \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m"),console.log(""),console.log(" Paste into your README.md:"),console.log(""),console.log(` \x1B[32m${n}\x1B[0m`),console.log(""),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(" Or paste into HTML:"),console.log(""),console.log(` \x1B[36m${o}\x1B[0m`),console.log(""),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(" Badge URL (live, updates every hour):"),console.log(` \x1B[90m${t}\x1B[0m`),console.log(""),console.log(" \x1B[2mThe badge shows your current lesson count \u2014 no auth required,"),console.log(" safe to embed in public repos. Updates hourly.\x1B[0m"),console.log(""),process.exit(0)}if(!process.argv[2]&&process.stdout.isTTY){console.log(""),console.log("\x1B[35m \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\x1B[0m"),console.log("\x1B[35m \u2551\x1B[0m \x1B[1m\u{1F9E0} cachly \u2014 Persistent AI Memory for Developers\x1B[0m \x1B[35m\u2551\x1B[0m"),console.log("\x1B[35m \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m"),console.log(""),console.log(" Stop re-teaching your AI every morning."),console.log(" cachly gives it a permanent brain \u2014 pre-briefed every session."),console.log("");const e=(t,s)=>console.log(` \x1B[36m ${t.padEnd(22)}\x1B[0m ${s}`);console.log(" \x1B[1mStart:\x1B[0m \x1B[1m\x1B[32mnpx @cachly-dev/mcp-server@latest autopilot\x1B[0m \u2190 \x1B[1mOne command, fully ready\x1B[0m"),console.log(" Every command below runs as: npx @cachly-dev/mcp-server@latest <command>"),console.log(""),console.log(" \x1B[1mEvery day:\x1B[0m"),e("status","Lessons, recalls and team at a glance"),e("ambient-stats","Tokens injected vs saved \u2014 the honest net, even when negative"),e("doctor","Diagnose a broken or missing install"),e("health","Check key, API, editor configs and git hook"),e("digest","What your Brain learned this week"),console.log(" \x1B[1mSet up:\x1B[0m"),e("demo","Try it first (no account)"),e("setup","Interactive setup (pick editors)"),e("init","Write the config for one editor (flags)"),e("claim","Keep a test Brain in your own account"),e("upgrade","Check for updates"),e("uninstall","Take cachly out of this project (--dry-run first)"),console.log(" \x1B[1mYour data:\x1B[0m"),e("export","Write your lessons to disk (md / jsonl)"),e("index [dir]","Index a project into the Brain (CI)"),e("learn-git [repo]","Learn lessons from recent commits (CI)"),console.log(" \x1B[1mTeam & sharing:\x1B[0m"),e("invite","Invite a teammate"),e("join <token>","Accept a Brain invite"),e("share","Share your Brain stats"),e("publish","Publish an importable Brain snapshot"),e("badge","README badge for your Brain"),console.log(" \x1B[1mMore:\x1B[0m"),e("bench","Recall quality vs flat-file memory"),e("tool-specs | openapi","Tool definitions for other agent frameworks"),console.log(""),console.log(" \x1B[90mWorks with: Claude Code \xB7 Cursor \xB7 Windsurf \xB7 GitHub Copilot \xB7 Cline \xB7 Zed\x1B[0m"),console.log(` \x1B[90mGDPR \xB7 German servers \xB7 ${ge.length} MCP tools\x1B[0m`),console.log(""),process.exit(0)}const vs={cursor:{datei:".cursor/hooks.json",name:"Cursor",wie:"session briefing, per-prompt recall after the first tool call"},copilot:{datei:Zo,name:"VS Code / Copilot",wie:"session briefing, per-prompt recall"},codex:{datei:Qo,name:"Codex",wie:"session briefing, per-prompt recall; trust it once in /hooks"},gemini:{datei:Xo,name:"Gemini CLI",wie:"session briefing, per-prompt recall"}};function ws(e,t,s){if(!t)return;const{datei:n,name:o,wie:a}=vs[e];if(t.hooks==="skipped"){console.log(`\u26A0\uFE0F ${s}${n} ${t.grund??"is not valid JSON"} \u2014 left untouched, ${o} hooks not wired`);return}const r=t.hooks==="unchanged"?"\u2713 ":"\u2705 ",c=t.hooks==="written"?"Written":t.hooks==="merged"?"Merged":"Already wired";console.log(`${r}${s}${c}: ${n} (Ambient Recall for ${o} \u2014 ${a})`)}async function Me(e,t,s,n){const o=e==="codex"?"Codex":"Gemini CLI",a=await(e==="codex"?dn:un)(t,s).catch(()=>null);if(!a||a.status==="skipped"){const u=hs(e,s);console.log(`\u26A0\uFE0F ${n}${a?`${a.datei} ${a.grund??"could not be read"} \u2014 left untouched.`:`${o} MCP entry could not be written.`} Register cachly yourself:`),u&&console.log(` ${n}${u.befehl}`);return}const r=a.status==="unchanged"?"\u2713 ":"\u2705 ",c=a.status==="written"?"Written":a.status==="updated"?"Updated":"Already configured";console.log(`${r}${n}${c}: ${a.datei} (MCP server cachly for ${o} \u2014 it asks once to trust this folder; until then the server stays off)`)}async function ye(e,t,s,n){const o={cursor:Ko,copilot:qo,codex:Go,gemini:Vo}[e];ws(e,await o(t,s).catch(()=>null),n)}if(process.argv[2]==="init"){const e=Date.now(),{writeFile:t,mkdir:s,readFile:n}=await import("node:fs/promises"),{resolve:o,dirname:a}=await import("node:path"),r=process.argv.slice(3),c=l=>{const i=r.indexOf(`--${l}`);return i!==-1?r[i+1]:void 0};let u=c("instance-id")??process.env.CACHLY_BRAIN_INSTANCE_ID,g=c("api-key")??process.env.CACHLY_JWT;const d=(c("editor")??"claude").toLowerCase(),m=o(c("project-dir")??"."),v=c("api-url");if(v&&(x=v.replace(/\/+$/,"")),!u||!g)try{const{existsSync:l}=await import("node:fs"),i=process.env.HOME??process.env.USERPROFILE??"",p=i?o(i,".claude","mcp.json"):"";if(p&&l(p)){const E=JSON.parse(await n(p,"utf-8")).mcpServers?.cachly?.env??{};g??=E.CACHLY_JWT,u??=E.CACHLY_BRAIN_INSTANCE_ID,g&&u&&console.log("\u2713 Reusing credentials from ~/.claude/mcp.json (saved by a prior setup)")}}catch{}if((!u||!g)&&(console.error(`
219
219
  Usage: npx @cachly-dev/mcp-server@latest init --instance-id <uuid> --api-key <cky_live_...> [--editor claude|cursor|windsurf|copilot|continue|codex|gemini] [--project-dir /path] [--api-url https://your-self-hosted-backend]
220
220
  `),console.error(`First time? Run the zero-config wizard (signs you in, auto-provisions): npx @cachly-dev/mcp-server@latest autopilot
221
221
  `),console.error(`Get your credentials from: ${R("/setup-ai","init")}
222
- `),process.exit(1)),d==="codex"||d==="gemini")X(g),console.log(""),await Me(d,m,Z(g,u),"");else{const l=me[d]??".mcp.json",i=o(m,l);await s(a(i),{recursive:!0});const{existsSync:p}=await import("node:fs"),h=p(i),E=h?await n(i,"utf-8").catch(()=>""):"";X(g);const O=await Nt(i,g,u,d,{readFile:n,existsSync:p});O!==E?(await t(i,O,"utf-8"),console.log(`
222
+ `),process.exit(1)),d==="codex"||d==="gemini")X(g),console.log(""),await Me(d,m,Z(g,u),"");else{const l=me[d]??".mcp.json",i=o(m,l);await s(a(i),{recursive:!0});const{existsSync:p}=await import("node:fs"),h=p(i),E=h?await n(i,"utf-8").catch(()=>""):"";X(g);const O=await Ot(i,g,u,d,{readFile:n,existsSync:p});O!==E?(await t(i,O,"utf-8"),console.log(`
223
223
  \u2705 ${h?"Updated":"Written"}: ${l}`)):console.log(`
224
- \u2713 Already configured: ${l} (no change)`)}const w=await Ot(m,u);for(const[l,i]of Object.entries(w)){const p=i==="updated"?"\u2705 Updated":i==="appended"?"\u2705 Appended to":"\u2705 Written",h=l.split(/[\\/]/).pop()??l;console.log(`${p}: ${h}`)}try{const l=await nt(m,u);l==="written"?console.log("\u2705 Written: .git/hooks/post-commit (CLS hook)"):l==="upgraded"?console.log(`\u2705 Upgraded: .git/hooks/post-commit (CLS hook \u2192 ${st})`):l==="appended"?console.log("\u2705 Appended: .git/hooks/post-commit (CLS hook)"):l==="unchanged"&&console.log("\u2713 CLS hook already current in .git/hooks/post-commit")}catch{}if(d==="claude")try{const l=await ct(m,u);if(l.settings==="skipped")console.log(`\u26A0\uFE0F .claude/settings.json ${l.grund??"is not valid JSON"} \u2014 left untouched, Ambient Recall hooks not wired`);else{const i=l.scripts==="written"?"\u2705 Written":l.scripts==="upgraded"?`\u2705 Upgraded (\u2192 ${lt})`:"\u2713 Unchanged";console.log(`${i}: .claude/hooks/ (Ambient Recall \u2014 session briefing, per-prompt recall, file briefing, auto-learn)`),l.settings==="written"?console.log("\u2705 Written: .claude/settings.json (Ambient Recall hooks wired)"):l.settings==="merged"?console.log("\u2705 Merged: .claude/settings.json (Ambient Recall hooks wired)"):console.log("\u2713 Ambient Recall hooks already wired in .claude/settings.json")}}catch{}(d==="cursor"||d==="copilot"||d==="codex"||d==="gemini")&&await ye(d,m,u,"");const b=((Date.now()-e)/1e3).toFixed(1);console.log(`
224
+ \u2713 Already configured: ${l} (no change)`)}const w=await Pt(m,u);for(const[l,i]of Object.entries(w)){const p=i==="updated"?"\u2705 Updated":i==="appended"?"\u2705 Appended to":"\u2705 Written",h=l.split(/[\\/]/).pop()??l;console.log(`${p}: ${h}`)}try{const l=await nt(m,u);l==="written"?console.log("\u2705 Written: .git/hooks/post-commit (CLS hook)"):l==="upgraded"?console.log(`\u2705 Upgraded: .git/hooks/post-commit (CLS hook \u2192 ${st})`):l==="appended"?console.log("\u2705 Appended: .git/hooks/post-commit (CLS hook)"):l==="unchanged"&&console.log("\u2713 CLS hook already current in .git/hooks/post-commit")}catch{}if(d==="claude")try{const l=await ct(m,u);if(l.settings==="skipped")console.log(`\u26A0\uFE0F .claude/settings.json ${l.grund??"is not valid JSON"} \u2014 left untouched, Ambient Recall hooks not wired`);else{const i=l.scripts==="written"?"\u2705 Written":l.scripts==="upgraded"?`\u2705 Upgraded (\u2192 ${lt})`:"\u2713 Unchanged";console.log(`${i}: .claude/hooks/ (Ambient Recall \u2014 session briefing, per-prompt recall, file briefing, auto-learn)`),l.settings==="written"?console.log("\u2705 Written: .claude/settings.json (Ambient Recall hooks wired)"):l.settings==="merged"?console.log("\u2705 Merged: .claude/settings.json (Ambient Recall hooks wired)"):console.log("\u2713 Ambient Recall hooks already wired in .claude/settings.json")}}catch{}(d==="cursor"||d==="copilot"||d==="codex"||d==="gemini")&&await ye(d,m,u,"");const b=((Date.now()-e)/1e3).toFixed(1);console.log(`
225
225
  \u{1F9E0} Cachly AI Brain configured for ${d==="claude"?"Claude Code":d}! (${b}s)`),console.log(d==="codex"||d==="gemini"?` Start ${d} in this folder and trust it when asked \u2014 the \`cachly\` MCP tools will appear.`:" Restart your editor \u2014 the `cachly` MCP tools will appear."),console.log(" Re-run `init` anytime \u2014 it's idempotent (only writes what changed)."),console.log(`
226
226
  \u{1F4DB} Add a live badge to your README:`),console.log(` npx @cachly-dev/mcp-server@latest badge
227
227
  `),process.exit(0)}if(process.argv[2]==="status"){const e=ne(),t=F();(!e||!t)&&(console.log(`
@@ -243,20 +243,20 @@ ${"\u2500".repeat(40)}`),a===0?console.log(`\u2705 All checks passed (${o} ok)
243
243
  `),process.exit(1)),process.exit(0)}if(process.argv[2]==="claim"){const{readFile:e,writeFile:t}=await import("node:fs/promises"),{existsSync:s}=await import("node:fs"),{resolve:n}=await import("node:path"),{homedir:o}=await import("node:os"),a=i=>{const p=process.argv.indexOf(`--${i}`);return p!==-1?process.argv[p+1]:void 0};x=(a("api-url")??"https://api.cachly.dev").replace(/\/+$/,"");const r=(a("auth-url")??mt).replace(/\/+$/,"");console.log(`
244
244
  \u{1F9E0} cachly \u2014 keep your test Brain in your own account
245
245
  `),(x!=="https://api.cachly.dev"||r!==mt)&&console.log(`\u26A0\uFE0F Not the cachly servers: API ${x}, sign-in ${r}
246
- `);const c=pn({argument:a("key"),credentials:Le()});c||(console.log("No test key (cky_trial_\u2026) was found in ~/.cachly/credentials.json."),console.log("If you already use your own cachly account, there is nothing to claim."),console.log(`Otherwise pass the key: npx @cachly-dev/mcp-server@latest claim --key cky_trial_\u2026
246
+ `);const c=mn({argument:a("key"),credentials:Le()});c||(console.log("No test key (cky_trial_\u2026) was found in ~/.cachly/credentials.json."),console.log("If you already use your own cachly account, there is nothing to claim."),console.log(`Otherwise pass the key: npx @cachly-dev/mcp-server@latest claim --key cky_trial_\u2026
247
247
  `),process.exit(1));const u=c.schluessel,g={argument:"--key",credentials:"~/.cachly/credentials.json"}[c.quelle];console.log(`Test key: ${u.slice(0,14)}\u2026 (from ${g})
248
248
  `);let d="";try{console.log(`Step 1: Sign in to your cachly account
249
- `);const i=await mn(fetch,r);console.log(` Code: \x1B[1;33m${i.userCode}\x1B[0m`),console.log(" Open this URL and confirm the code:"),console.log(`${i.adresse}
249
+ `);const i=await gn(fetch,r);console.log(` Code: \x1B[1;33m${i.userCode}\x1B[0m`),console.log(" Open this URL and confirm the code:"),console.log(`${i.adresse}
250
250
  `),ce(i.adresse,(p,h)=>{p||console.log(`
251
- \u2139\uFE0F No browser could be opened here (${h??"unknown"}) \u2014 please open the URL yourself.`)}),process.stdout.write(" Waiting for sign-in"),d=await gn(fetch,i,{basis:r,puls:()=>process.stdout.write(".")}),console.log(` \x1B[32m\u2713\x1B[0m
251
+ \u2139\uFE0F No browser could be opened here (${h??"unknown"}) \u2014 please open the URL yourself.`)}),process.stdout.write(" Waiting for sign-in"),d=await fn(fetch,i,{basis:r,puls:()=>process.stdout.write(".")}),console.log(` \x1B[32m\u2713\x1B[0m
252
252
  `)}catch(i){console.error(`
253
253
 
254
254
  ${i.message}. Nothing was changed.
255
- `),process.exit(1)}process.stdout.write("Step 2: Moving the test Brain into your account (it restarts once, up to a minute)\u2026");let m;try{m=await fn(fetch,x,d,u,"mcp",`cachly-mcp/${D}`),console.log(` \x1B[32m\u2713\x1B[0m
255
+ `),process.exit(1)}process.stdout.write("Step 2: Moving the test Brain into your account (it restarts once, up to a minute)\u2026");let m;try{m=await hn(fetch,x,d,u,"mcp",`cachly-mcp/${D}`),console.log(` \x1B[32m\u2713\x1B[0m
256
256
  `)}catch(i){console.error(`
257
257
 
258
- ${bn(i)}
259
- `),process.exit(1)}y=m.apiKey,X(m.apiKey),ut(m.instanceId,{apiKey:m.apiKey}),await ue(m.apiKey),await pe(m.instanceId);const v=o(),w=new Set([...Object.values(me).map(i=>n(process.cwd(),i)),n(v,".claude.json"),n(v,".claude","mcp.json"),n(v,".cursor","mcp.json"),n(v,".codeium","windsurf","mcp_config.json")]);let b=0;for(const i of w)if(s(i))try{const p=vn(await e(i,"utf-8"),u,m.apiKey);p.ersetzt&&(await t(i,p.text,"utf-8"),b++)}catch{}const l=await hn(fetch,x,m.apiKey,m.instanceId);console.log(yn(m,l)),console.log(`
258
+ ${vn(i)}
259
+ `),process.exit(1)}y=m.apiKey,X(m.apiKey),ut(m.instanceId,{apiKey:m.apiKey}),await ue(m.apiKey),await pe(m.instanceId);const v=o(),w=new Set([...Object.values(me).map(i=>n(process.cwd(),i)),n(v,".claude.json"),n(v,".claude","mcp.json"),n(v,".cursor","mcp.json"),n(v,".codeium","windsurf","mcp_config.json")]);let b=0;for(const i of w)if(s(i))try{const p=wn(await e(i,"utf-8"),u,m.apiKey);p.ersetzt&&(await t(i,p.text,"utf-8"),b++)}catch{}const l=await yn(fetch,x,m.apiKey,m.instanceId);console.log(bn(m,l)),console.log(`
260
260
  New key stored in ~/.cachly/credentials.json${b?` and in ${b} editor config file(s) that held the test key`:""}.`),console.log(`Restart your editor (or reload its MCP servers) so it uses the new key.
261
261
  `),process.exit(0)}const $e=process.argv[2]==="autopilot";if(process.argv[2]==="autosetup"||process.argv[2]==="setup"||$e){const{writeFile:e,mkdir:t,readFile:s}=await import("node:fs/promises"),{existsSync:n}=await import("node:fs"),{resolve:o,dirname:a}=await import("node:path"),{createInterface:r}=await import("node:readline"),c=Date.now(),u=$e?"autopilot":"setup",g=process.argv.indexOf("--api-url");g!==-1&&process.argv[g+1]&&(x=process.argv[g+1].replace(/\/+$/,""),console.log(`\u2139\uFE0F Using self-hosted backend: ${x}
262
262
  `)),S("setup_started");const d=process.stdin.isTTY===!0,m=$e||process.argv.includes("--yes")||process.argv.includes("-y")||!d;$e?console.log(`\u{1F916} Autopilot \u2014 one-command setup (auth \u2192 instance \u2192 configs \u2192 Brain), zero prompts.
@@ -293,7 +293,7 @@ Could not create an instance automatically. Opening ${R("/instances",u)} \u2026
293
293
  `),ce(R("/instances",u)),v.close(),process.exit(1))}const i=l[0];l.length>1&&(console.log(`\u2139\uFE0F Multiple instances found \u2014 using most recent: ${i.name}`),console.log(` (Run with --instance-id <id> to use a different one)
294
294
  `)),console.log(`\u2713 Instance: ${i.name} (${i.id.slice(0,8)}\u2026)
295
295
  `),S("setup_instance_ready",{instance_id:i.id});const p=process.cwd(),h=[],{homedir:E}=await import("node:os"),O=E();h.push("claude"),n(o(p,".cursor"))&&h.push("cursor"),n(o(p,".windsurf"))&&h.push("windsurf"),n(o(p,".vscode"))&&h.push("copilot"),n(o(p,".continue"))&&h.push("continue");const j=process.platform==="darwin"?o(O,"Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev"):o(O,".vscode/extensions");(n(j)&&!h.includes("copilot")||n(j))&&h.push("cline");const A=process.platform==="darwin"?o(O,"Library/Application Support/Zed"):o(O,".config/zed");n(A)&&h.push("zed");const C=f=>({claude:"Claude Code",cursor:"Cursor",windsurf:"Windsurf",copilot:"GitHub Copilot",continue:"Continue.dev",cline:"Cline (VSCode)",zed:"Zed"})[f]??f,T=h;console.log(`Step 3: Configuring for: ${T.map(C).join(", ")}
296
- `);const{existsSync:$}=await import("node:fs"),k=[];let L=0;X(b);for(const f of T){const _=me[f]??".mcp.json",I=o(p,_);try{await t(a(I),{recursive:!0});const U=$(I),z=await Nt(I,b,i.id,f,{readFile:s,existsSync:$});await e(I,z,"utf-8"),console.log(`\u2705 ${U?"Updated":"Written"}: ${_}`),L++}catch(U){const z=U.message;console.log(`\u26A0\uFE0F Could not write ${_}: ${z}`),console.log(" Fix permissions or run with sudo, then re-run setup."),k.push({file:_,reason:z})}}const Y=o(O,".claude","mcp.json");try{await t(a(Y),{recursive:!0});let f={};if(n(Y))try{f=JSON.parse(await s(Y,"utf-8"))}catch{}f.mcpServers??={},f.mcpServers.cachly={command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(b,i.id,{includeApiKey:!0})},await e(Y,JSON.stringify(f,null,2),"utf-8"),console.log("\u2705 Written: ~/.claude/mcp.json (global \u2014 works in every project)"),L++}catch(f){const _=f.message;console.log(`\u26A0\uFE0F Could not write ~/.claude/mcp.json: ${_}`),k.push({file:"~/.claude/mcp.json",reason:_})}const K=await Ot(p,i.id);for(const[f,_]of Object.entries(K))console.log(`${_==="updated"?"\u2705 Updated":_==="appended"?"\u2705 Appended to":"\u2705 Written"}: ${f}`);y=b;let be=0;if(n(o(p,".git"))){process.stdout.write("\u23F3 Bootstrapping Brain from git history (~10s)...");try{const f=await J("brain_from_git",{instance_id:i.id,repo_path:p,limit:100}),_=f.match(/Ingested: \*\*(\d+)\*\*/);be=_?parseInt(_[1],10):0,console.log(` \u2713 ${be} lesson${be===1?"":"s"} extracted from git history`);const I=Mo(f);I&&(console.log(""),console.log(I))}catch(f){console.log(` (skipped: ${f.message.slice(0,80)})`)}}if(be===0){process.stdout.write("\u{1F331} Seeding 16 universal engineering lessons (first recall works instantly)...");try{await J("brain_seed_starter",{instance_id:i.id}),console.log(" \u2713");try{const f=await J("smart_recall",{instance_id:i.id,query:"docker build slow"}),_=es(f);_&&(console.log(""),console.log(Do(_.topic,_.what)))}catch{}}catch(f){console.log(` (skipped: ${f.message.slice(0,60)})`)}}process.stdout.write(`
296
+ `);const{existsSync:$}=await import("node:fs"),k=[];let L=0;X(b);for(const f of T){const _=me[f]??".mcp.json",I=o(p,_);try{await t(a(I),{recursive:!0});const U=$(I),z=await Ot(I,b,i.id,f,{readFile:s,existsSync:$});await e(I,z,"utf-8"),console.log(`\u2705 ${U?"Updated":"Written"}: ${_}`),L++}catch(U){const z=U.message;console.log(`\u26A0\uFE0F Could not write ${_}: ${z}`),console.log(" Fix permissions or run with sudo, then re-run setup."),k.push({file:_,reason:z})}}const Y=o(O,".claude","mcp.json");try{await t(a(Y),{recursive:!0});let f={};if(n(Y))try{f=JSON.parse(await s(Y,"utf-8"))}catch{}f.mcpServers??={},f.mcpServers.cachly={command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:Z(b,i.id,{includeApiKey:!0})},await e(Y,JSON.stringify(f,null,2),"utf-8"),console.log("\u2705 Written: ~/.claude/mcp.json (global \u2014 works in every project)"),L++}catch(f){const _=f.message;console.log(`\u26A0\uFE0F Could not write ~/.claude/mcp.json: ${_}`),k.push({file:"~/.claude/mcp.json",reason:_})}const K=await Pt(p,i.id);for(const[f,_]of Object.entries(K))console.log(`${_==="updated"?"\u2705 Updated":_==="appended"?"\u2705 Appended to":"\u2705 Written"}: ${f}`);y=b;let be=0;if(n(o(p,".git"))){process.stdout.write("\u23F3 Bootstrapping Brain from git history (~10s)...");try{const f=await J("brain_from_git",{instance_id:i.id,repo_path:p,limit:100}),_=f.match(/Ingested: \*\*(\d+)\*\*/);be=_?parseInt(_[1],10):0,console.log(` \u2713 ${be} lesson${be===1?"":"s"} extracted from git history`);const I=Do(f);I&&(console.log(""),console.log(I))}catch(f){console.log(` (skipped: ${f.message.slice(0,80)})`)}}if(be===0){process.stdout.write("\u{1F331} Seeding 16 universal engineering lessons (first recall works instantly)...");try{await J("brain_seed_starter",{instance_id:i.id}),console.log(" \u2713");try{const f=await J("smart_recall",{instance_id:i.id,query:"docker build slow"}),_=ts(f);_&&(console.log(""),console.log(Uo(_.topic,_.what)))}catch{}}catch(f){console.log(` (skipped: ${f.message.slice(0,60)})`)}}process.stdout.write(`
297
297
  \u23F3 Fetching your Brain health preview...`);try{const f=await fetch(`${x}/api/v1/instances/${i.id}/memory`,{headers:{Authorization:`Bearer ${b}`},signal:AbortSignal.timeout(6e3)});if(f.ok){const _=await f.json(),I=_.lesson_count??0,U=_.context_count??0,z=Math.round(_.iq_boost_pct??0),te=I===0?"Intern \u{1F331}":I<10?"Junior Dev \u{1F527}":I<30?"Mid Dev \u26A1":I<60?"Senior Dev \u{1F9E0}":I<100?"Staff Eng \u{1F680}":"Principal Eng \u{1F3C6}";if(console.log(` \u2713
298
298
  `),console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),console.log("\u2502 \u{1F9E0} Brain Health Report \u2502"),console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log(`\u2502 Lessons stored : ${String(I).padEnd(6)} \u2502 Level: ${te.padEnd(20)}\u2502`),console.log(`\u2502 Context entries : ${String(U).padEnd(6)} \u2502 Quality score: ${String(z).padEnd(3)}% \u2502`),I===0&&(console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),console.log("\u2502 Your Brain is empty and ready to learn. \u2502"),console.log("\u2502 After your first coding session it will contain: \u2502"),console.log("\u2502 \u2022 Lessons from every bug you fixed \u2502"),console.log("\u2502 \u2022 Your project's indexed files \u2502"),console.log("\u2502 \u2022 A session summary for next time \u2502")),console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),I>=5){process.stdout.write(`
299
299
  \u23F3 Running predictive risk scan...`);try{const B=(await J("brain_predict",{instance_id:i.id,context:"starting a new development session"})).split(`
@@ -335,14 +335,14 @@ Could not create an instance automatically. Opening ${R("/instances",u)} \u2026
335
335
  \u2705 Brain learned from your commits.
336
336
  `),await new Promise(c=>setTimeout(c,300))}catch(r){console.error(`
337
337
  \u274C learn-git failed: ${r.message}
338
- `),process.exit(1)}process.exit(0)}if(process.argv[2]==="ambient-recall"&&await Xo(process.argv.slice(3),process.argv[1]),process.argv[2]==="ambient-auffrischen"&&await en(),process.argv[2]===on&&await tn(process.argv[3]??""),process.argv[2]==="ambient-credit"){try{const e=Math.round(Number(process.argv[3]));if(Number.isFinite(e)&&e>0){const t=Math.min(e,2e4),s=process.argv.slice(4).join(" ").slice(0,200)||void 0,n={ts:new Date().toISOString(),event:"credit",injected:0,prevented:t,note:s,instanz:F()||void 0};await sn(n),await os(process.env.CACHLY_BRAIN_INSTANCE_ID??N,n),console.log(`\u2705 ambient credit recorded: ${t} tokens${s?` (${s})`:""}`)}else console.error("Usage: npx @cachly-dev/mcp-server@latest ambient-credit <tokens-saved> [note]")}catch{}process.exit(0)}if(process.argv[2]==="ambient-stats"){const e=F(),t=cn(await dt(),e||void 0),s=t.eigene,n=Gn(s),o=s.slice(-5);if(console.log(`
338
+ `),process.exit(1)}process.exit(0)}if(process.argv[2]==="ambient-recall"&&await en(process.argv.slice(3),process.argv[1]),process.argv[2]==="ambient-auffrischen"&&await tn(),process.argv[2]===nn&&await on(process.argv[3]??""),process.argv[2]==="ambient-credit"){try{const e=Math.round(Number(process.argv[3]));if(Number.isFinite(e)&&e>0){const t=Math.min(e,2e4),s=process.argv.slice(4).join(" ").slice(0,200)||void 0,n={ts:new Date().toISOString(),event:"credit",injected:0,prevented:t,note:s,instanz:F()||void 0};await rn(n),await ns(process.env.CACHLY_BRAIN_INSTANCE_ID??N,n),console.log(`\u2705 ambient credit recorded: ${t} tokens${s?` (${s})`:""}`)}else console.error("Usage: npx @cachly-dev/mcp-server@latest ambient-credit <tokens-saved> [note]")}catch{}process.exit(0)}if(process.argv[2]==="ambient-stats"){const e=F(),t=ln(await dt(),e||void 0),s=t.eigene,n=Vn(s),o=s.slice(-5);if(console.log(`
339
339
  \u{1F9E0} Ambient Recall \u2014 net-token ledger
340
340
  `),console.log(` Instance: ${e?`${e.slice(0,8)}\u2026`:"(none set \u2014 showing every entry on this computer)"}`),console.log(` Turns recorded: ${s.length}`),console.log(` Injected tokens: ${n.injected}`),console.log(` Prevented tokens: ${n.prevented} (agent-reported via ambient-credit)`),console.log(` NET: ${n.net>=0?"+":""}${n.net} tokens`),o.length>0){console.log(`
341
341
  Last entries:`);for(const a of o){const r=a.prevented>0?`+${a.prevented} prevented`:`-${a.injected} injected`;console.log(` \u2022 ${a.ts.slice(0,19)} ${a.event}: ${r}${a.note?` (${a.note})`:""}`)}}(t.ohneInstanz>0||t.andereInstanzen>0)&&(console.log(`
342
- Not counted above:`),t.ohneInstanz>0&&console.log(` \u2022 ${t.ohneInstanz} older entries without an instance (before 0.10.207)`),t.andereInstanzen>0&&console.log(` \u2022 entries of ${t.andereInstanzen} other instance${t.andereInstanzen===1?"":"s"} on this computer`)),console.log(""),process.exit(0)}if(process.argv[2]==="uninstall"){const{homedir:e,tmpdir:t}=await import("node:os"),{resolve:s}=await import("node:path"),{createInterface:n}=await import("node:readline/promises"),o=process.argv.slice(3),r={projekt:s((c=>{const u=o.indexOf(`--${c}`);return u!==-1?o[u+1]:void 0})("project-dir")??"."),heim:process.env.HOME??process.env.USERPROFILE??process.env.APPDATA??e(),tmp:t(),schluessel:ne(),instanz:F(),apiUrl:x,istTty:process.stdin.isTTY===!0&&process.stdout.isTTY===!0,ausgabe:c=>console.log(c),frage:async c=>{const u=n({input:process.stdin,output:process.stdout});try{return/^(y|yes|j|ja)$/i.test((await u.question(c)).trim())}finally{u.close()}}};process.exit(await Jo(o,r))}if(process.argv[2]==="doctor"){const{checkNodeVersion:e,checkCredential:t,checkApiReachable:s,checkAuthAccepted:n,checkInstance:o,checkVectorCoverage:a,inspectAmbientHooks:r,checkHooks:c,checkLedger:u,renderDoctorReport:g,doctorExitCode:d}=await import("./doctor.js"),m=[e(),t(y),await s(x),await n(x,y),o(F()||N),await a(x,y,F()||N),c(r(process.cwd())),u(await dt(),rn())];console.log(g(m)),process.exit(d(m))}if(process.argv[2]==="cls-ingest"){try{const e=process.argv[3];(!e||!y)&&process.exit(0);let t;try{t=JSON.parse(e)}catch{process.exit(0)}const s=t.instance_id??N;(!s||!t.source)&&process.exit(0),await J("cls_ingest",{instance_id:s,source:t.source,payload:t.payload??{}})}catch{}process.exit(0)}if(!y){const e=Ut();if(e)try{const t=await fetch(Jt(e),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:zt(e),signal:AbortSignal.timeout(8e3)});if(t.ok){const s=await t.json();s.access_token&&(y=s.access_token,le(s.access_token),S("m2m_auth_completed",{grant:"client_credentials",client_id:e.clientId}),process.stderr.write(`\u{1F916} cachly: authenticated via client_credentials (M2M, headless).
342
+ Not counted above:`),t.ohneInstanz>0&&console.log(` \u2022 ${t.ohneInstanz} older entries without an instance (before 0.10.207)`),t.andereInstanzen>0&&console.log(` \u2022 entries of ${t.andereInstanzen} other instance${t.andereInstanzen===1?"":"s"} on this computer`)),console.log(""),process.exit(0)}if(process.argv[2]==="uninstall"){const{homedir:e,tmpdir:t}=await import("node:os"),{resolve:s}=await import("node:path"),{createInterface:n}=await import("node:readline/promises"),o=process.argv.slice(3),r={projekt:s((c=>{const u=o.indexOf(`--${c}`);return u!==-1?o[u+1]:void 0})("project-dir")??"."),heim:process.env.HOME??process.env.USERPROFILE??process.env.APPDATA??e(),tmp:t(),schluessel:ne(),instanz:F(),apiUrl:x,istTty:process.stdin.isTTY===!0&&process.stdout.isTTY===!0,ausgabe:c=>console.log(c),frage:async c=>{const u=n({input:process.stdin,output:process.stdout});try{return/^(y|yes|j|ja)$/i.test((await u.question(c)).trim())}finally{u.close()}}};process.exit(await Fo(o,r))}if(process.argv[2]==="doctor"){const{checkNodeVersion:e,checkCredential:t,checkApiReachable:s,checkAuthAccepted:n,checkInstance:o,checkVectorCoverage:a,inspectAmbientHooks:r,checkHooks:c,checkLedger:u,renderDoctorReport:g,doctorExitCode:d}=await import("./doctor.js"),m=[e(),t(y),await s(x),await n(x,y),o(F()||N),await a(x,y,F()||N),c(r(process.cwd())),u(await dt(),an())];console.log(g(m)),process.exit(d(m))}if(process.argv[2]==="cls-ingest"){try{const e=process.argv[3];(!e||!y)&&process.exit(0);let t;try{t=JSON.parse(e)}catch{process.exit(0)}const s=t.instance_id??N;(!s||!t.source)&&process.exit(0),await J("cls_ingest",{instance_id:s,source:t.source,payload:t.payload??{}})}catch{}process.exit(0)}if(!y){const e=zt();if(e)try{const t=await fetch(Ft(e),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:Jt(e),signal:AbortSignal.timeout(8e3)});if(t.ok){const s=await t.json();s.access_token&&(y=s.access_token,le(s.access_token),S("m2m_auth_completed",{grant:"client_credentials",client_id:e.clientId}),process.stderr.write(`\u{1F916} cachly: authenticated via client_credentials (M2M, headless).
343
343
  `))}else process.stderr.write(`\u26A0\uFE0F cachly: client_credentials auth failed (${t.status}). Check CACHLY_CLIENT_ID / CACHLY_CLIENT_SECRET.
344
344
  `),S("m2m_auth_failed",{status:t.status})}catch(t){process.stderr.write(`\u26A0\uFE0F cachly: client_credentials auth error: ${t.message}
345
- `),S("m2m_auth_failed",{reason:"network"})}}const ws=["demo","share","publish","health","autosetup","setup","autopilot","init","digest","invite","badge","join","upgrade","bench","tool-specs","openapi","ambient-credit","ambient-stats","doctor","uninstall"];if(!y&&!ws.includes(process.argv[2]??""))if(!process.argv[2]&&process.stdout.isTTY===!0&&Vt){const n=`
345
+ `),S("m2m_auth_failed",{reason:"network"})}}const xs=["demo","share","publish","health","autosetup","setup","autopilot","init","digest","invite","badge","join","upgrade","bench","tool-specs","openapi","ambient-credit","ambient-stats","doctor","uninstall"];if(!y&&!xs.includes(process.argv[2]??""))if(!process.argv[2]&&process.stdout.isTTY===!0&&Zt){const n=`
346
346
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
347
347
  \u2551 \u{1F9E0} cachly AI Brain \u2014 Setup required \u2551
348
348
  \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
@@ -362,7 +362,7 @@ Could not create an instance automatically. Opening ${R("/instances",u)} \u2026
362
362
  \u{1F9E0} cachly: no API key yet \u2014 the first cachly tool call creates a test Brain on EU servers, no sign-up.
363
363
  (Own account: npx @cachly-dev/mcp-server@latest autopilot)
364
364
 
365
- `);else{const e=Ht(y);if(e!==null){const t=Math.floor((e-Date.now())/6e4);t<=0?process.stderr.write(`
365
+ `);else{const e=Mt(y);if(e!==null){const t=Math.floor((e-Date.now())/6e4);t<=0?process.stderr.write(`
366
366
  \u26A0\uFE0F cachly: CACHLY_JWT expired ${Math.abs(t)} minute(s) ago.
367
367
  All tool calls will fail. Get a fresh token at ${R("/setup-ai","jwt-expiry")}
368
368
 
@@ -370,7 +370,7 @@ Could not create an instance automatically. Opening ${R("/instances",u)} \u2026
370
370
  \u26A0\uFE0F cachly: CACHLY_JWT expires in ${t} minute(s).
371
371
  Refresh it soon at ${R("/setup-ai","jwt-expiry")} to avoid interruptions.
372
372
 
373
- `)}}process.env.PORT||Nn({aktuelle:Ln(D)});const ke=process.env.PORT?parseInt(process.env.PORT,10):void 0;if(ke){const{createServer:e}=await import("node:http");e((s,n)=>{const o=new URL(s.url??"/",`http://localhost:${ke}`);if(o.pathname==="/health"){n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({status:"ok",version:D,zeroResults:{total:Co,last10:Ao.slice(-10).map(a=>({query:a.query,ts:new Date(a.ts).toISOString()}))}}));return}if(o.pathname==="/mcp"||o.pathname==="/"){(async()=>{const a=ms(),r=new Qt({sessionIdGenerator:void 0});n.on("close",()=>{r.close().catch(()=>{}),a.close().catch(()=>{})});try{await a.connect(r),await r.handleRequest(s,n)}catch(c){process.stderr.write(`cachly-mcp HTTP: ${c.message}
373
+ `)}}process.env.PORT||On({aktuelle:Nn(D)});const ke=process.env.PORT?parseInt(process.env.PORT,10):void 0;if(ke){const{createServer:e}=await import("node:http");e((s,n)=>{const o=new URL(s.url??"/",`http://localhost:${ke}`);if(o.pathname==="/health"){n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({status:"ok",version:D,zeroResults:{total:To,last10:Co.slice(-10).map(a=>({query:a.query,ts:new Date(a.ts).toISOString()}))}}));return}if(o.pathname==="/mcp"||o.pathname==="/"){(async()=>{const a=gs(),r=new Xt({sessionIdGenerator:void 0});n.on("close",()=>{r.close().catch(()=>{}),a.close().catch(()=>{})});try{await a.connect(r),await r.handleRequest(s,n)}catch(c){process.stderr.write(`cachly-mcp HTTP: ${c.message}
374
374
  `),n.headersSent||(n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:c.message})))}})();return}n.writeHead(404),n.end("not found")}).listen(ke,()=>{process.stderr.write(`cachly-mcp HTTP server listening on :${ke}
375
- `)})}else{const e=new Zt;W("start: Module geladen, stdio verbinden"),await V.connect(e),typeof process.argv[2]=="string"&&!process.argv[2].startsWith("-")||(Qe=!0,io(),Mn(),Pn(process.stdin,s=>{process.stderr.write(`[cachly-mcp] ${s} \u2014 Session zu Ende, Server geht mit
376
- `),(async()=>{await He().catch(()=>{});for(const[,n]of se)await n.quit().catch(()=>{});process.exit(0)})()}))}Je("cachly","startup",{version:D,mode:process.env.MCP_HTTP_PORT?"http":"stdio"}).catch(()=>{});export{rt as CLAUDE_MD_MARKER_END,it as CLAUDE_MD_MARKER_START,So as CROSS_LINGUAL_MAP,me as EDITOR_FILES,xo as STOPWORDS,$o as arabicLightStem,ys as buildClaudeMdBlock,hs as buildMcpConfig,Z as buildServerEnv,ko as expandCrossLingual,wo as extractTimestamp,_o as katakanaToRomaji,bo as levenshtein,fs as mcpAddBefehl,Nt as mergeMcpConfig,vo as recencyBoost,To as reconnectOnce,yo as splitMultiQuery,po as startBackgroundDevicePoll,ho as tokenize,Ot as writeInstructions};
375
+ `)})}else{const e=new Qt;W("start: Module geladen, stdio verbinden"),await V.connect(e),typeof process.argv[2]=="string"&&!process.argv[2].startsWith("-")||(Qe=!0,ro(),Dn(),Hn(process.stdin,s=>{process.stderr.write(`[cachly-mcp] ${s} \u2014 Session zu Ende, Server geht mit
376
+ `),(async()=>{await He().catch(()=>{});for(const[,n]of se)await n.quit().catch(()=>{});process.exit(0)})()}))}Je("cachly","startup",{version:D,mode:process.env.MCP_HTTP_PORT?"http":"stdio"}).catch(()=>{});export{rt as CLAUDE_MD_MARKER_END,it as CLAUDE_MD_MARKER_START,Ao as CROSS_LINGUAL_MAP,me as EDITOR_FILES,_o as STOPWORDS,ko as arabicLightStem,bs as buildClaudeMdBlock,ys as buildMcpConfig,Z as buildServerEnv,So as expandCrossLingual,xo as extractTimestamp,$o as katakanaToRomaji,vo as levenshtein,hs as mcpAddBefehl,Ot as mergeMcpConfig,wo as recencyBoost,Eo as reconnectOnce,bo as splitMultiQuery,mo as startBackgroundDevicePoll,yo as tokenize,Pt as writeInstructions};
package/dist/src/tools.js CHANGED
@@ -1,4 +1,4 @@
1
- const e=[{name:"list_instances",description:"List all your cachly cache instances with their status and connection details. Read-only. Returns an array of instance objects \u2014 each with id, name, tier, status, region, RAM, and redis:// connection string. Returns an empty array if no instances exist. No pagination: all instances are returned in one call (typical accounts have < 20). Use this first to discover instance UUIDs required by get_instance, cache_get, cache_set, and all other cache tools. Use get_instance to retrieve full metadata for a single instance.",inputSchema:{type:"object",properties:{},required:[]}},{name:"create_instance",description:"Create a new managed Valkey/Redis cache instance on cachly.dev. Free tier provisions in ~30 seconds. Paid tiers return a Stripe checkout URL. Available tiers: free (25 MB), dev (200 MB, \u20AC19/mo), pro (900 MB, \u20AC49/mo), speed (900 MB Dragonfly + Semantic Cache, \u20AC79/mo), business (7 GB, \u20AC199/mo).",inputSchema:{type:"object",properties:{name:{type:"string",description:"Unique name for the instance (min 3 chars)"},tier:{type:"string",enum:["free","dev","pro","speed","business"],description:'Pricing tier. Start with "free" for testing.'}},required:["name","tier"]}},{name:"get_instance",description:"Get full metadata for a specific cache instance: name, tier, status (provisioning / running / paused), region, RAM limit, Redis connection string, created_at, and expiry. Read-only. Returns an error if the instance_id is not found or belongs to another account. Call list_instances first to discover valid UUIDs. Use get_connection_string instead if you only need the redis:// URL for your app config.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance (from list_instances)"}},required:[]}},{name:"get_connection_string",description:"Get the Redis/Valkey connection string (redis:// URL) for a running instance. Use this to configure your application or set environment variables.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance."}},required:[]}},{name:"delete_instance",description:"Permanently delete a cache instance. Deprovisions the Kubernetes workload and removes all data. This action is irreversible.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance to delete"},confirm:{type:"boolean",description:"Must be true to confirm deletion"}},required:["confirm"]}},{name:"cache_get",description:"Get a value from a running cache instance by key. Returns the stored value (string or deserialized JSON object) or null if the key does not exist or has expired. Read-only \u2014 no side effects. Use cache_mget when you need multiple keys in one round-trip. Use cache_exists to check existence without retrieving the value. Use semantic_search when you need fuzzy/vector search across stored values.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance."},key:{type:"string",description:"Cache key to retrieve"},org_id:{type:"string",description:"Optional org ID \u2014 if the direct key misses, falls back to the shared org namespace org:{org_id}:sem:{key}"}},required:["key"]}},{name:"cache_set",description:'Set a key-value pair in a running cache instance. Overwrites any existing value at the key \u2014 not idempotent for new data. Returns "OK" on success; returns an error if the instance_id is invalid or the instance is paused. Value can be a string or a JSON-serialized object. Optionally set a TTL in seconds (omit for no expiry). Use cache_mset instead for setting multiple keys in a single pipeline round-trip. Use cache_stream_set instead for caching LLM token streams (ordered string chunks).',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"},key:{type:"string",description:"Cache key"},value:{type:"string",description:"Value to store (string or JSON)"},ttl:{type:"number",description:"Time-to-live in seconds (optional, omit for no expiry)"},org_id:{type:"string",description:"Optional org ID \u2014 also writes the key to the shared org namespace org:{org_id}:sem:{key} with the same TTL"}},required:["key","value"]}},{name:"cache_delete",description:"Permanently delete one or more keys from a running cache instance (uses Redis DEL). This operation is destructive and irreversible \u2014 deleted keys cannot be recovered. Deleting a non-existent key is safe and returns 0 for that key (no error). Returns the count of keys that were actually deleted (existing keys only). Use this to explicitly remove stale entries; prefer cache_set with a short TTL for auto-expiring data. Do NOT use this to clear an entire instance \u2014 use the dashboard or delete_instance for that.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance to delete keys from (get from list_instances)"},keys:{type:"array",items:{type:"string"},description:"One or more cache keys to delete. Accepts exact keys only (no glob patterns \u2014 use cache_keys to list first)."}},required:["keys"]}},{name:"cache_exists",description:"Check whether one or more keys exist in a running cache instance (uses Redis EXISTS). Read-only \u2014 no side effects. Returns the count of keys that currently exist (integer 0 to N). If none of the keys exist, returns 0. If all exist, returns the total key count passed in. Duplicate keys in the input array are each counted separately (Redis behavior). Use this to check presence before a cache_get to avoid null handling, or to verify a cache warm-up completed. Use cache_get instead if you also need the value; use cache_ttl if you need expiry info.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance to check (get from list_instances)"},keys:{type:"array",items:{type:"string"},description:"Keys to check for existence. Accepts exact keys only (no glob patterns)."}},required:["keys"]}},{name:"cache_ttl",description:"Get the remaining time-to-live (TTL) of a key in seconds. Returns -1 if the key exists but has no expiry, -2 if the key does not exist. Read-only \u2014 no side effects. Use cache_set with a ttl parameter to set or update the expiry.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"},key:{type:"string",description:"Cache key to inspect"}},required:["key"]}},{name:"cache_keys",description:'List keys in a cache instance matching an optional glob pattern (e.g. "user:*", "session:*"). Uses SCAN to avoid blocking the server. Returns at most `count` keys.',inputSchema:{type:"object",properties:{instance_id:{type:"string"},pattern:{type:"string",description:"Glob pattern (default: *)"},count:{type:"number",description:"Max keys to return (default: 50, max: 500)"}},required:[]}},{name:"cache_stats",description:"Get real-time stats for a cache instance: memory usage, hit/miss rate, commands/sec, connected clients, keyspace info, and uptime. Read-only \u2014 no side effects. The instance_id identifies the target instance (obtain from list_instances). Use this for monitoring, capacity planning, or debugging performance issues \u2014 not for reading cached values (use cache_get for that). Use cache_exists or cache_ttl if you only need key-level information.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"}},required:[]}},{name:"semantic_search",description:'Find cached entries that are semantically similar to a natural-language query. Read-only \u2014 no side effects. Returns an array of objects, each with: key, value, similarity_score (0\u20131), and namespace. Returns an empty array if no entries meet the similarity threshold. Requires OPENAI_API_KEY (or compatible provider) and the Speed/Business tier with CACHLY_VECTOR_URL. Embeddings are computed server-side and never leave Germany (pgvector HNSW index). Example: "find all cached responses about password reset" or "what did we answer about pricing?". Use cache_get for exact key lookup; use smart_recall for brain lessons.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"},query:{type:"string",description:"Natural-language query to find similar cached content"},threshold:{type:"number",description:"Minimum cosine similarity 0\u20131 (default: 0.82). Lower = broader matches."},namespace:{type:"string",description:"Semantic namespace to search in (default: cachly:sem)"},top_k:{type:"number",description:"Maximum number of results to return (default: 5)"},use_hybrid:{type:"boolean",description:"Enable Hybrid BM25+Vector RRF fusion search. Passes `hybrid: true` and the query text to the pgvector API for higher precision on named entities. Default: false."},auto_namespace:{type:"boolean",description:"Auto-detect the namespace from the query text using text heuristics instead of using the `namespace` parameter. Returns results only from the matching domain (code/translation/summary/qa/creative)."}},required:["query"]}},{name:"detect_namespace",description:"Classify a prompt into one of 5 semantic namespaces using text heuristics. Overhead: <0.1 ms, no embedding required. Useful to understand which namespace cachly will use for a given prompt. Returns one of: cachly:sem:code, cachly:sem:translation, cachly:sem:summary, cachly:sem:qa, cachly:sem:creative.",inputSchema:{type:"object",properties:{prompt:{type:"string",description:"The text prompt to classify into a semantic namespace"}},required:["prompt"]}},{name:"cache_warmup",description:"Pre-warm the semantic cache with a list of prompt/value pairs. For each entry: computes an embedding, checks if a similar entry already exists (similarity \u2265 0.98), and writes new entries to Valkey + pgvector index. Use this to seed FAQ responses, product descriptions, or known-good LLM answers before the first real user traffic. Requires OPENAI_API_KEY.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},entries:{type:"array",description:"List of prompt/value pairs to pre-warm into the cache",items:{type:"object",properties:{prompt:{type:"string",description:"The query or question to cache"},value:{type:"string",description:"The answer or response to store for this prompt"},namespace:{type:"string",description:"Optional per-entry namespace override"}},required:["prompt","value"]}},namespace:{type:"string",description:"Default namespace for all entries (default: cachly:sem)"},ttl:{type:"number",description:"Time-to-live in seconds for warmed entries (omit for no expiry)"},auto_namespace:{type:"boolean",description:"Auto-detect the namespace per prompt using text heuristics. Overrides `namespace` when no per-entry namespace is set."}},required:["entries"]}},{name:"index_project",description:"Index local source files into the cachly semantic cache so AI assistants can use semantic_search to find relevant files instead of re-reading the whole codebase every time. Walks a directory recursively, reads each matching file, and stores a summary + path as a semantic cache entry (prompt = file path + content excerpt, value = relative path). Requires an embedding provider (OPENAI_API_KEY or CACHLY_EMBED_PROVIDER + key). Run once, then re-run after major refactors. TTL=86400 (24h) keeps entries fresh.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cachly instance"},dir:{type:"string",description:"Absolute path to the directory to index (e.g. /Users/you/myproject/src)"},extensions:{type:"array",items:{type:"string"},description:'File extensions to include (default: ["ts","js","go","py","java","rs","md","kt","swift"])'},max_files:{type:"number",description:"Maximum number of files to index (default: 100)"},ttl:{type:"number",description:"TTL in seconds for indexed entries (default: 86400 = 24 h)"},summary_chars:{type:"number",description:"Characters to use as summary per file (default: 1200)"},namespace:{type:"string",description:"Semantic namespace to store under (default: cachly:sem:code)"}},required:["dir"]}},{name:"cache_mset",description:"Set multiple key-value pairs in a single pipeline round-trip. Supports per-key TTL \u2013 unlike native MSET. Uses one TCP round-trip for N keys via Redis pipeline. Each item overwrites any existing value for that key. On partial failure the successfully pipelined keys are committed; a per-key error list is returned for any that failed. Returns a summary: { set: N, errors: [...] }. Use cache_set for a single key; use cache_stream_set for large streaming payloads.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},items:{type:"array",description:"Key-value pairs to set",items:{type:"object",properties:{key:{type:"string",description:"Cache key"},value:{description:"Value to store (JSON-serialised)"},ttl:{type:"number",description:"Per-key TTL in seconds (optional)"}},required:["key","value"]}}},required:["items"]}},{name:"cache_mget",description:"Retrieve multiple keys in one round-trip using native Redis MGET. Returns values in the same order as the keys array; missing keys are null.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},keys:{type:"array",items:{type:"string"},description:"List of keys to fetch"}},required:["keys"]}},{name:"cache_lock_acquire",description:"Acquire a distributed lock using Redis SET NX PX (Redlock-lite). Returns a fencing token on success. The lock auto-expires after ttl_ms to prevent deadlocks. Use cache_lock_release to free the lock early.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:"Lock resource identifier"},ttl_ms:{type:"number",description:"Safety TTL in milliseconds (e.g. 5000)"},retries:{type:"number",description:"Max acquire attempts (default: 3)"},retry_delay_ms:{type:"number",description:"Milliseconds between retries (default: 50)"}},required:["key","ttl_ms"]}},{name:"cache_lock_release",description:"Release a previously acquired distributed lock. Uses a Lua script for atomic release \u2013 only deletes the key if the fencing token matches.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:"Lock resource identifier (same as in cache_lock_acquire)"},token:{type:"string",description:"Fencing token returned by cache_lock_acquire"}},required:["key","token"]}},{name:"get_api_status",description:"Full diagnostic for your cachly Brain \u2014 call this FIRST whenever anything is not working. Returns: API reachability, JWT validity + expiry, your user ID, all Brain instances with live status (\u{1F7E2} running / \u{1F7E1} provisioning / \u{1F534} stopped), Redis ping on the active connection, and actionable fix steps for every issue found. Workflow: run get_api_status \u2192 read the issue it flags \u2192 fix it \u2192 retry your tool.",inputSchema:{type:"object",properties:{},required:[]}},{name:"remember_context",description:'Save context information to the cache so you can recall it later without re-computing. Perfect for caching: codebase overviews, file summaries, project structure, frequently-accessed data, or "thinking" results like dependency analysis. The AI assistant can use this to avoid re-reading the entire codebase every time. Overwrites any existing value stored under the same key. Returns { key, stored_at, ttl } confirming the saved context. Example: remember_context("project overview", "This is a Next.js app with...") then later: recall_context("project overview"). Use recall_context to retrieve; use list_remembered to see all stored keys.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:'Descriptive key like "project_overview", "auth_architecture", "file:src/index.ts"'},content:{type:"string",description:"The context/summary/analysis to remember"},category:{type:"string",enum:["overview","architecture","file_summary","dependency","thinking","custom"],description:"Category for organization (default: custom)"},ttl:{type:"number",description:"Time-to-live in seconds (default: 86400 = 24h, use 0 for no expiry)"}},required:["key","content"]}},{name:"recall_context",description:'Retrieve previously saved context from the cache. Returns the saved content or null if not found. Use this at the START of any task to check if you already have relevant context cached, before doing expensive operations like reading many files. Supports glob patterns: "file:*" matches all file summaries, "arch*" matches architecture-related keys.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:'The key to look up (supports glob pattern like "file:*")'}},required:["key"]}},{name:"list_remembered",description:"List all cached context entries for this project. Shows what knowledge the AI assistant has already cached, so you can decide whether to recall existing context or refresh it. Returns: key, category, size, TTL remaining, and a content preview.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},category:{type:"string",enum:["overview","architecture","file_summary","dependency","thinking","custom","all"],description:"Filter by category (default: all)"},limit:{type:"number",description:"Max entries to return (default: 50)"}},required:[]}},{name:"forget_context",description:'Delete one or more cached context entries. Use when context is stale or you want to force a fresh analysis. Supports glob patterns: "file:*" deletes all file summaries.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},keys:{type:"array",items:{type:"string"},description:"Keys to delete (supports glob)"}},required:["keys"]}},{name:"learn_from_attempts",description:'Store a lesson AFTER any non-trivial task (deploy, debug, fix, decision); future sessions recall it. instance_id may be omitted; the configured instance is used \u2014 never guess one. If the topic already exists, the write is an UPDATE and REQUIRES `grund` (one line: WHY the previous version was wrong) \u2014 without it the update is rejected. A conflicting outcome triggers a contradiction warning. Example: learn_from_attempts(topic="deploy:api", outcome="success", what_worked="PORT 3095 SERVES WHISPER \u2014 nohup docker compose up -d", severity="critical")',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:'Short slug, e.g. "deploy:web", "debug:redis-tls", "fix:generate-series"'},outcome:{type:"string",enum:["success","failure","partial"],description:"Did it work?"},what_worked:{type:"string",description:"What solved it. LEAD WITH THE DECISIVE FACT (the number, address, command) in the first 100 characters \u2014 briefings truncate there."},what_failed:{type:"string",description:"What did NOT work (optional but valuable)"},context:{type:"string",description:'The SYMPTOM in the words someone would search for, before they knew the fix ("deploy stuck, rollout not finishing"). Plus error messages and root cause. Optional but high-value: it is what closes the gap between the question and the answer.'},severity:{type:"string",enum:["critical","major","minor"],description:"Impact severity: critical (blocks work/deploy), major (significant slowdown), minor (nice to know). Default: major."},file_paths:{type:"array",items:{type:"string"},description:'Files involved in this lesson (e.g. ["infra/deploy.sh", ".env"])'},commands:{type:"array",items:{type:"string"},description:'Commands that worked or failed (e.g. ["rsync -avz ...", "docker compose up -d"])'},tags:{type:"array",items:{type:"string"},description:'Topic tags for filtering (e.g. ["bash", "deploy", "env"])'},depends_on:{type:"array",items:{type:"string"},description:'Prerequisites, e.g. ["node:>=20", "docker:running"]. A stale one flags dependent lessons for review.'},author:{type:"string",description:'Who is storing this lesson, e.g. "alice". Enables cross-author reuse tracking.'},grund:{type:"string",description:"One line: WHY the previous version was wrong, like a commit message. REQUIRED when the topic already exists; optional on first write."},ersetzt:{type:"string",description:'Topic slug of an OLDER lesson this one refutes. The old one is suppressed in smart_recall and shows a "superseded" banner.'},gilt_ab:{type:"string",description:"ISO date since when the FACT is true (vs. when written). Omit if unknown. A replacement sets gilt_bis on the old lesson (then shown as history). About the fact, NOT an age decay."},gilt_fuer:{type:"array",items:{type:"string"},description:'Version scope of the FACT, package@range, all must hold: ["next@<15", "react@>=18 <19", "go@>=1.22", "python:fastapi@<0.110"]. Recall ranks a lesson that does not fit the project versions last, marked, never hidden; unknown = no change. Omit on update to keep, [] clears.'},visibility:{type:"string",enum:["team","private","public"],description:'Who can see it: "team" (default) = all members, "private" = only via exact recall_best_solution, "public" = same as team for now.'},service:{type:"string",description:'Service or system this lesson concerns, e.g. "prometheus", "auth-service".'},service_kind:{type:"string",enum:["service","system"],description:'"service" (default) for an application, "system" for infrastructure like prometheus or redis.'},group:{type:"string",description:'Optional sub-team scope, e.g. "backend". Only that group and admins see it. Independent of visibility.'}},required:["topic","outcome","what_worked"]}},{name:"recall_best_solution",description:'Recall the best known solution for a topic from past lessons. Call this BEFORE attempting any task that might have been done before. Returns the most recent successful lesson for the topic, with confidence indicator. \u26A0\uFE0F badge = lesson is >5d old (verify before applying). \u{1F534} = >10d old (likely stale!). Recalling a lesson resets its confidence clock to 1.0 (marks as recently verified). A superseded lesson shows a banner naming its successor; if the successor was deleted, the banner says so and the lesson counts as the best remaining answer. Example: recall_best_solution(topic="deploy:web") \u2192 returns the working deploy command.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:'Topic slug to look up, e.g. "deploy:web". Supports partial match.'},author:{type:"string",description:'Who is asking (optional). Required to read a group-scoped lesson: without it, group-scoped lessons answer like "not found". Team-wide lessons need no author.'}},required:["topic"]}},{name:"recall_feedback",description:'Tell the Brain whether a recalled lesson ACTUALLY SOLVED your problem. This is the one signal the Brain cannot infer: recall_count only means "it was shown". Call it right after a lesson helped you \u2014 or after you solved something the Brain should have found. IMPORTANT: rank=0 means the lesson was NOT in the answer at all. That is the most valuable feedback there is, because it says the ranking never surfaced it. Example: recall_feedback(query="why does the deploy abort", topic="ci:timeout-reports-cancelled", helped=true, rank=0)',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},query:{type:"string",description:"The question you asked, as you asked it. Not a summary \u2014 the ranking has to learn from the real wording."},topic:{type:"string",description:"Topic of the lesson this is about. May be a lesson that was NOT returned (then set rank=0)."},helped:{type:"boolean",description:'Did it solve your problem? true or false. There is deliberately no "not sure" \u2014 a guessed value is worse than none.'},rank:{type:"number",description:"Where it stood in the answer, 1-based. 0 = it was NOT in the answer at all (the most valuable case)."},note:{type:"string",description:"Optional: what was missing, or why it did not fit."},author:{type:"string",description:"Optional: your handle."}},required:["query","topic","helped","rank"]}},{name:"lesson_verified",description:'Report that you RAN the check for a lesson and what came back. This is the only way a lesson gets fresh again \u2014 reading one does not verify it, and since 0.10.165 recall no longer pretends otherwise. The server never runs the command itself: you run it, you report the result. holds=true refreshes verified_at and clears any earlier failed check. holds=false marks the lesson as questionable and REQUIRES `befund` \u2014 it deletes nothing, because a check can fail for reasons that have nothing to do with the lesson. Example: lesson_verified(topic="deploy:web", holds=true, geprueft_mit="curl -s http://127.0.0.1:3220/bereit")',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:"Topic slug of the lesson you checked."},haelt:{type:"boolean",description:'Did the check confirm the lesson? true or false. There is deliberately no "not sure": guessing true makes an unchecked lesson look fresh, guessing false flags a correct one.'},geprueft_mit:{type:"string",description:"The command you actually ran, one line. Shown in later recalls so the next reader can repeat it."},befund:{type:"string",description:"What you saw instead. REQUIRED when haelt=false \u2014 a warning without content is noise."},author:{type:"string",description:"Optional: your handle."}},required:["topic","haelt"]}},{name:"smart_recall",description:'Semantically search cached context using natural language. instance_id may be omitted; the configured instance is used \u2014 never guess one.Instead of exact key matching, finds context by meaning. Example: smart_recall("how does authentication work") \u2192 returns cached auth architecture summary. Falls back to remember_context keys if no semantic match is found.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},query:{type:"string",description:"Natural language query to find relevant cached context"},threshold:{type:"number",description:"Similarity threshold 0-1 (default: 0.78)"},author:{type:"string",description:"Who is recalling (optional). Counts cross-author reuse."},context_files:{type:"array",items:{type:"string"},description:'Files you are working on, e.g. ["src/auth/service.ts"]. Lessons learned around these files rank higher.'}},required:["query"]}},{name:"session_start",description:"Single-call session briefing. Call this at the START of every session INSTEAD of multiple separate smart_recall/recall_best_solution calls. Returns: last session summary, recent lessons sorted by recency, relevant lessons for your focus area, brain health stats (counts of open failures and stale lessons), team telepathy (what teammates learned this week), predictive pre-warnings (if your focus area has known failure patterns), and memory crystals (compressed wisdom from old sessions). Also saves a session start marker so session_end can compute duration.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},focus:{type:"string",description:'Keywords for what you plan to work on today (e.g. "deploy infra api"). Used to surface relevant lessons at the top.'},author:{type:"string",description:'Your name or handle (e.g. "alice"). Enables Team Telepathy \u2014 filters YOUR lessons vs TEAM lessons from past 7 days.'},provider:{type:"string",description:'Current AI provider (e.g. "claude-code", "copilot", "cursor", "windsurf"). Shown in the briefing header and saved so the next provider can see who was last active.'},workspace_path:{type:"string",description:"Absolute path to the project root. If no session_end was found (e.g. context limit hit), reads git log to reconstruct what happened since last session."}},required:[]}},{name:"session_start_summary",description:"Focused session briefing for large brains. Returns only the top-N most relevant lessons for the given focus topic, scored by relevance, recall count, severity, recency, and outcome. Ideal when session_start returns too many lessons to fit in context (1000+ lesson brains). Use session_start for the full briefing including handoffs, streak, roadmap, and team telepathy.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},focus:{type:"string",description:'The topic or task you are about to work on (e.g. "deploy infra", "api auth"). Used to score and rank lessons by relevance.'},top_n:{type:"number",description:"Number of lessons to return (default 10, max 25). Lessons are ranked by relevance to focus."},author:{type:"string",description:"Your name or handle (optional). Same as session_start \u2014 used for team lesson filtering."}},required:["focus"]}},{name:"session_end",description:'Save a session summary when you finish working. Records what was accomplished, files changed, and lesson count. The next session_start will show this summary as "Last session". Call this when ending a work session, before going idle, or before summarizing. Ambient Learning: if workspace_path is provided, reads git log since session start and auto-learns from commits.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},summary:{type:"string",description:"Brief summary of what was accomplished this session (2-3 sentences)"},files_changed:{type:"array",items:{type:"string"},description:"Key files changed this session (optional)"},lessons_learned:{type:"number",description:"Number of new lessons stored this session (optional)"},workspace_path:{type:"string",description:'Absolute path to the project root (e.g. "/Users/you/myproject"). Enables Ambient Learning \u2014 reads git log since session start and auto-learns from commit messages.'}},required:["summary"]}},{name:"session_handoff",description:'Save a detailed handoff for the NEXT chat window / session. Stores: current progress, TODO list (done + remaining), changed files with descriptions, instructions for the next assistant, and any incomplete work. The next session_start automatically includes this handoff so the new window knows EXACTLY what happened and what remains. Call this BEFORE closing a chat window, especially if work is incomplete. This prevents the "continue" problem where new windows lose context, skip tasks, or produce broken code.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},completed_tasks:{type:"array",items:{type:"string"},description:'Tasks that were fully completed (e.g. "Implemented brainSearch() in JS SDK")'},remaining_tasks:{type:"array",items:{type:"string"},description:"Tasks NOT yet done \u2014 the next window MUST pick these up"},files_changed:{type:"array",items:{type:"object",properties:{path:{type:"string",description:"File path relative to project root"},status:{type:"string",enum:["complete","partial","broken"],description:"State of this file"},description:{type:"string",description:"What was changed and what still needs work"}},required:["path","status"]},description:"Changed files with their current state \u2014 marks partial/broken files so next window knows to fix them"},instructions:{type:"string",description:"Free-form instructions for the next assistant. Be specific: what to do next, what to avoid, what broke."},context_summary:{type:"string",description:"Brief summary of what happened this session (architecture decisions, key findings, blockers)"},blocked_on:{type:"string",description:'If work is blocked, describe what is needed to unblock (e.g. "waiting for API deploy", "needs user input on design")'}},required:["completed_tasks","remaining_tasks"]}},{name:"session_ping",description:"Lightweight checkpoint \u2014 call this every ~5 tool calls or whenever you complete a significant step. Stores the current task + files touched so session_start on the NEXT provider can reconstruct what happened even if session_end was never called (e.g. Claude context limit hit, window crashed). This solves the provider-switching problem: Claude \u2192 Copilot \u2192 Cursor all see the same last checkpoint. Extremely fast \u2014 one Redis SET, no blocking operations.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},task:{type:"string",description:'What you are currently working on (e.g. "Implementing invite handler in handler/invite.go")'},files_touched:{type:"array",items:{type:"string"},description:"Files modified so far this session"},next_step:{type:"string",description:"What the NEXT step is after this checkpoint (helps next provider resume immediately)"},provider:{type:"string",description:'Current AI provider (e.g. "claude-code", "copilot", "cursor", "windsurf")'}},required:["task"]}},{name:"auto_learn_session",description:"Auto-learn from a list of session observations WITHOUT explicit learn_from_attempts calls. Pass what happened (commands run, errors seen, solutions found) and the brain classifies and stores lessons automatically. Use at session_end to capture everything you did, even if you forgot to call learn_from_attempts. Returns a summary of what was auto-stored.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},observations:{type:"array",description:"List of observations from this session",items:{type:"object",properties:{action:{type:"string",description:"What was tried (command, approach, code change)"},outcome:{type:"string",enum:["success","failure","partial"],description:"Result"},details:{type:"string",description:"Error message, output, or explanation"},topic:{type:"string",description:"Optional topic key (auto-generated if omitted)"},severity:{type:"string",enum:["critical","major","minor"],description:"Severity (default: minor)"}},required:["action","outcome"]}}},required:["observations"]}},{name:"brain_who_knows",description:'Find who in your team has the most expertise on a given topic. Queries the org-wide knowledge graph (built automatically from learn_from_attempts author fields) and returns a ranked list of contributors whose lessons match the query, ordered by lesson count and confidence. Use to find the right person to ask before starting a task, or to understand knowledge distribution. Example: brain_who_knows(topic="kubernetes deployment") \u2192 "\u{1F947} alice \u2014 5 lessons, 94% confidence".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:"Topic or question to find experts for"},limit:{type:"number",description:"Max number of experts to return (default: 10)"}},required:["topic"]}},{name:"brain_file_map",description:'Show what cachly knows about a list of files \u2014 experts + related lessons per file. Call this before starting work on unfamiliar files, or in sync_file_changes to see what knowledge exists. For each file path: shows who has previously touched it (from learn_from_attempts author+file_paths) and which lessons reference it. Example: brain_file_map(file_paths=["src/auth/jwt.ts"]) \u2192 "\u{1F947} alice (3\xD7 \xB7 today) \u2014 related: fix:jwt-expiry".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},file_paths:{type:"array",items:{type:"string"},description:"File paths to look up (max 10)"}},required:["file_paths"]}},{name:"team_expertise_map",description:"Full team expertise overview \u2014 who knows what, at a glance. Returns a ranked table of all contributors with their lesson count, top domains, and last-active date. Use for onboarding (who to ask about X?), retrospectives, or to find knowledge gaps. Built automatically from learn_from_attempts(author=...) calls \u2014 no setup needed.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},top_n:{type:"number",description:"Max contributors to show (default: 20)"}},required:[]}},{name:"brain_collab_pairs",description:`Show the Person\u2194Person Collaboration Graph for your team (W5). Lists every pair of contributors who have worked together \u2014 either by touching the same files in learn_from_attempts or by recalling each other's lessons via smart_recall(requester=...). Each pair includes a "Frag @X und @Y" routing suggestion \u2014 ideal for onboarding and bus-factor analysis. Also flags solo contributors whose knowledge no teammate has yet recalled (bus-factor risk). Example: brain_collab_pairs() \u2192 "@alice \u2194 @bob \u2014 12 events \xB7 ask them together about auth/payments".`,inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},min_weight:{type:"number",description:"Minimum collaboration events to show a pair (default: 1)"}},required:[]}},{name:"brain_portability",description:"Bring your own model, keep your brain: the same memory in every AI editor. Returns your Brain ID plus ready-to-paste MCP config snippets for every compatible AI client: Claude Code, Cursor, Windsurf, GitHub Copilot (VS Code), Cline, Zed, Continue. All 7 clients connect to the same Brain \u2014 same lessons, crystals, predictions, and team data. Use autopilot to configure all detected editors in one command. Example: brain_portability() \u2192 config blocks for 7 clients + model-neutrality proof table.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."}},required:[]}},{name:"skill_gaps",description:"Show knowledge blind spots in your Brain \u2014 domains with unresolved failures, lessons with missing attribution, and areas where brain_who_knows cannot help. Run periodically to find where to focus knowledge capture effort. Returns a prioritized list: \u{1F534} critical (failures with no solutions) \u2192 \u{1F7E1} warn \u2192 \u{1F535} info. Pairs with brain_coverage for a full knowledge-health picture.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},min_failures:{type:"number",description:"Min failure count to flag a domain as a gap (default: 1)"}},required:[]}},{name:"brain_coverage",description:'Knowledge-coverage health score for your codebase \u2014 scored 0-100. Reports: total lessons, success ratio, attribution completeness, team engagement, and file coverage vs git ls-files. Run after brain_from_git or periodically to track knowledge-capture progress. Use skill_gaps to find what to fix. Example: brain_coverage() \u2192 "\u{1F7E2} Overall score: 78/100 \xB7 42 lessons \xB7 6 contributors \xB7 31% files covered".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},repo_path:{type:"string",description:"Path to the git repository (default: current directory)"}},required:[]}},{name:"brain_metrics",description:`Report the three decisive Brain metrics: (1) time-to-first-recall (onboarding friction), (2) recall-lift vs. raw BM25 (the moat proof, from Cachly-Bench), and (3) team-knowledge-reuse \u2014 what % of proven recalls used a teammate's lesson. Use to track whether the Brain is delivering its core value. Pass author="handle" to smart_recall so cross-author reuse can be measured.`,inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."}},required:[]}},{name:"brain_changelog",description:`Generate a human-readable Markdown changelog of lessons learned in the last N days. Groups lessons by topic category, annotates with author, recall count and confidence. Ideal for weekly standups, sprint retros, or async team updates \u2014 share the output directly in Slack or a doc. Example: brain_changelog(instance_id="...", days=7) \u2192 grouped Markdown changelog of the week's learning.`,inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},days:{type:"number",description:"How many days back to include (default: 7)"},max_lessons:{type:"number",description:"Maximum number of lessons to include (default: 30)"},include_failures:{type:"boolean",description:"Include failure-outcome lessons (default: true)"}},required:[]}},{name:"brain_service_map",description:'Map everything the Brain knows about a running service or system: who operates it, which files run in it, every known failure, and every proven fix. Built from lessons tagged with `service="..."` in learn_from_attempts. Ideal for incident triage \u2014 when a service is misbehaving (e.g. a restarting pod), instantly surface who knows it and what has gone wrong with it before. Example: brain_service_map(service="prometheus") \u2192 operators, known OOM failures, and the fixes that worked.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},service:{type:"string",description:'Name of the service/system to map (e.g. "prometheus", "cachly-web", "auth-service"). Matches the `service` tag on stored lessons.'}},required:["service"]}},{name:"sync_file_changes",description:"Associate recent file changes with brain knowledge. Pass a list of changed file paths (from `git diff --stat`). Returns lessons relevant to those files, and records the file changes in session history. Call this after commits so the brain tracks what changed and why.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},changed_files:{type:"array",items:{type:"string"},description:"List of changed file paths"},git_diff_stat:{type:"string",description:"Output of `git diff --stat` (optional)"},commit_msg:{type:"string",description:"Commit message (optional)"}},required:["changed_files"]}},{name:"team_learn",description:'Store a lesson in a shared team brain so all team members benefit. Like learn_from_attempts, but REQUIRES an author name for attribution. Shows up in team_recall with "by <author>" so the team knows who learned it.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},author:{type:"string",description:"Your name or handle (required for team attribution)"},topic:{type:"string",description:'Topic in category:keyword format (e.g. "deploy:api")'},outcome:{type:"string",enum:["success","failure","partial"],description:"What happened"},what_worked:{type:"string",description:"What worked (the solution)"},what_failed:{type:"string",description:"What did NOT work (avoid this)"},severity:{type:"string",enum:["critical","major","minor"],description:"Impact level"},file_paths:{type:"array",items:{type:"string"},description:"Relevant file paths"},commands:{type:"array",items:{type:"string"},description:"Commands that worked"},tags:{type:"array",items:{type:"string"},description:"Tags for categorization"}},required:["author","topic","outcome","what_worked"]}},{name:"team_confirm",description:"Endorse (review-confirm) a team lesson so trusted, human-reviewed knowledge ranks above unreviewed auto-learned entries. A senior review weighs more than a peer review; distinct endorsements add a small boost. Confirmed lessons surface higher in smart_recall and team_recall and carry a \u{1F6E1}\uFE0F/\u2714\uFE0F badge. Use this in code review or knowledge reviews to bless the canonical solution for a topic.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},topic:{type:"string",description:'Topic slug of the lesson to confirm (e.g. "deploy:api")'},reviewer:{type:"string",description:"Your name or handle (the reviewer endorsing this lesson)"},level:{type:"string",enum:["senior","peer"],description:'Review weight \u2014 "senior" ranks higher than "peer" (default: peer)'},note:{type:"string",description:"Optional review note (kept in the lesson audit trail)"}},required:["topic","reviewer"]}},{name:"team_assign_role",description:'Assign a role (admin | reviewer | contributor | viewer) to a team member on a shared brain instance. Roles control what each person can do: admin can manage roles and delete lessons; reviewer can senior-review (\u{1F6E1}\uFE0F badge, stronger recall boost); contributor can store lessons and peer-review (\u2714\uFE0F badge); viewer is read-only. First call bootstraps governance (no auth required when no admins exist yet). After that, only an admin can assign or change roles. Example: team_assign_role(handle="alice", role="reviewer", assigned_by="bob") \u2014 bob must be an admin.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Handle or name of the team member to assign a role to"},role:{type:"string",enum:["admin","reviewer","contributor","viewer"],description:"Role to assign. admin: manage roles + all actions. reviewer: senior-review (\u{1F6E1}\uFE0F). contributor: store + peer-review. viewer: read-only."},assigned_by:{type:"string",description:"Handle of the admin performing the assignment (required after governance bootstrap)"}},required:["handle","role"]}},{name:"team_whoami",description:"Show your own role and capabilities on a shared brain instance. Tells you what you can do (store, review, manage roles) and who to contact if you need a higher role. Run this after onboarding to confirm your role was set correctly.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Your handle or name"}},required:["handle"]}},{name:"team_roster",description:"Show all team members and their assigned roles on a shared brain instance. Returns a table of handles, roles (\u{1F451} admin \xB7 \u{1F6E1}\uFE0F reviewer \xB7 \u270F\uFE0F contributor \xB7 \u{1F441}\uFE0F viewer), and capabilities. Use during onboarding to see who can do what, or to verify role assignments.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"}},required:[]}},{name:"team_audit",description:'View the governance audit log for a shared brain \u2014 an immutable trail of who changed roles and who confirmed which lessons, with timestamps. Essential for enterprise compliance and security reviews. Admin-only once governance is active (an admin has been assigned). Events are recorded automatically on team_assign_role and team_confirm \u2014 no setup. Example: team_audit(requester="alice") \u2192 "\u{1F451} role: bob set carol viewer \u2192 contributor \xB7 \u2705 confirm: dave confirmed auth:jwt-skew (senior)".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},requester:{type:"string",description:"Your handle \u2014 must be an admin once governance is active"},limit:{type:"number",description:"Max events to show, newest first (default: 50, max: 200)"}},required:[]}},{name:"team_grant_scope",description:'Add or remove a team member to/from a named group (sub-team) on a shared brain. Group-scoped lessons (stored with group="...") only surface in smart_recall for members of that group (and admins). This is team-level visibility, orthogonal to lesson-level private. Admin-gated after the role model is bootstrapped. Example: team_grant_scope(handle="alice", group="security", assigned_by="bob") \u2014 bob must be admin.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Handle of the team member to add/remove"},group:{type:"string",description:'Group/sub-team name (e.g. "backend", "security", "platform")'},action:{type:"string",enum:["add","remove"],description:"add (default) or remove the member from the group"},assigned_by:{type:"string",description:"Handle of the admin performing the change (required after governance bootstrap)"}},required:["handle","group"]}},{name:"team_scopes",description:"List team groups and their members, or the groups a specific person belongs to. Pass handle to see one person's scopes; omit it to see all groups on the instance. Use to audit who can see group-scoped lessons.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Optional \u2014 show only this person's group memberships"}},required:[]}},{name:"team_recall",description:"Recall lessons from a shared team brain, showing who learned what. Works on any shared instance (all team members using the same instance_id). Shows author, recency, and severity for each lesson. Use this to onboard new team members or find who knows about a topic.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},topic:{type:"string",description:"Topic or keyword to filter lessons (optional)"},author:{type:"string",description:"Filter by author name (optional)"},limit:{type:"number",description:"Max lessons to return (default: 10)"}},required:[]}},{name:"team_synthesize",description:"Team Brain Synthesis \u2014 merge multiple contributors' lessons on the same topic into one canonical version. When 2+ developers store lessons for the same topic with different details, this proposes the best merged version. Shows: all contributions by author, what worked (consensus), what failed (union), canonical lesson to store. Use this when onboarding new team members or before documenting a process.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},topic:{type:"string",description:'Topic slug to synthesize (e.g. "deploy:api")'}},required:["topic"]}},{name:"memory_crystalize",description:"Compress the last 30-50 sessions and auto-learned lessons into a dense Memory Crystal. A crystal is a compact, structured summary of everything the brain learned \u2014 grouped by category (deploy, fix, debug, \u2026). Crystals survive session cleanup and appear in session_start once enough sessions have accumulated. Run this monthly or after a big milestone to preserve institutional knowledge. Returns a digest of what was crystallized.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},label:{type:"string",description:'Optional label for this crystal (e.g. "Q1 2026", "v2 launch"). Auto-generated from date if omitted.'}},required:[]}},{name:"team_crystallize",description:'Create a Team Crystal \u2014 the team-wide, causal counterpart to memory_crystalize. Where memory_crystalize compresses ONE brain by category, team_crystallize surfaces what a per-user memory structurally cannot: which fixes solved structurally SIMILAR problems across MULTIPLE people. A pattern only crystallizes when 2+ distinct authors independently converged on it \u2014 that cross-person signal is the moat against single-user "Dreaming"-style memory. Needs attributed lessons (learn_from_attempts(author=...) / team_learn). Surfaces in crystal_view. Example: team_crystallize() \u2192 "\u{1F9E9} pool \u2014 3 people converged (alice, bob, carol): bounded pool + timeout".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},min_authors:{type:"number",description:"Min distinct authors that must converge for a pattern to crystallize (default: 2, min: 2)"},label:{type:"string",description:'Optional label (e.g. "Q1 2026"). Auto-generated from date if omitted.'}},required:[]}},{name:"roadmap_add",description:"Add a new item to the persistent project roadmap stored in the Brain. Items survive across sessions and editors \u2014 the roadmap is always up to date. Use for features, bugs, refactors, or any planned work. Call roadmap_list to see all open items, roadmap_next to get the next actionable item.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},title:{type:"string",description:"Short title of the task/feature (3\u201310 words)"},description:{type:"string",description:"What needs to be done, acceptance criteria, context"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Priority level (default: medium)"},tags:{type:"array",items:{type:"string"},description:'Tags for filtering (e.g. ["api", "web", "sdk", "infra"])'},milestone:{type:"string",description:"Milestone/epic this belongs to (optional)"}},required:["title"]}},{name:"roadmap_update",description:"Update the status, priority, or details of a roadmap item. Use to move items through the lifecycle: planned \u2192 in-progress \u2192 done (or blocked/cancelled). Also use to add notes/findings while working on an item.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},id:{type:"string",description:"Item ID returned by roadmap_add or roadmap_list"},status:{type:"string",enum:["planned","in-progress","done","blocked","cancelled"],description:"New status for the item"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Updated priority (optional)"},notes:{type:"string",description:"Progress notes, findings, or blockers (appended to existing notes)"},title:{type:"string",description:"Updated title (optional)"},description:{type:"string",description:"Updated description (optional)"}},required:["id"]}},{name:"roadmap_list",description:"List all roadmap items, optionally filtered by status, priority, tag, or milestone. Returns items sorted by priority then creation date. Called automatically by session_start to show open work.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},status:{type:"string",enum:["planned","in-progress","done","blocked","cancelled","open"],description:"Filter by status. Use 'open' to see planned+in-progress+blocked (default: open)"},tag:{type:"string",description:"Filter by tag (optional)"},milestone:{type:"string",description:"Filter by milestone (optional)"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Filter by minimum priority (optional)"}},required:[]}},{name:"roadmap_next",description:"Get the single most important next actionable roadmap item. Returns the highest-priority in-progress item first, then planned items, sorted by priority. Call at session start to immediately know what to work on next.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},tag:{type:"string",description:"Filter by tag (optional)"}},required:[]}},{name:"brain_doctor",description:"Check the health of your AI Brain and get actionable recommendations. Reports: lesson count, context entries, last session age, open failures, quality score, effective IQ boost, stale index. Returns a prioritized list of issues with fix instructions.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},workspace_path:{type:"string",description:"Absolute path to workspace root \u2014 enables package.json analysis for openclaw cross-promo (optional)"}},required:[]}},{name:"brain_hygiene",description:"Autonomously sweep and maintain your Brain \u2014 flags stale lessons as provisional, archives long-dormant ones, and resolves contradictions where success clearly dominates failure. Safe to run on a schedule (weekly CI job) or on-demand before a big release. Lesson state lifecycle: active \u2192 provisional (confidence < threshold) \u2192 archived (stale + low-recall + old). Archived lessons are excluded from smart_recall but preserved for audit. dry_run=true (default false) shows what would change without writing anything.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},dry_run:{type:"boolean",description:"Report changes without applying them (default false)"},provisional_threshold:{type:"number",description:"Confidence below which a lesson is flagged provisional (default 0.5)"},archive_days:{type:"number",description:"Days after which a provisional low-recall lesson is archived (default 30)"}},required:[]}},{name:"global_learn",description:'Store a lesson that applies across ALL your projects (cross-project knowledge). Idempotent: if a lesson with the same topic already exists, it is updated in place \u2014 no duplicates are created. Returns a confirmation with the stored lesson key. No rate limits. Global lessons are stored with the prefix cachly:global:lesson: and recalled from any instance via global_recall. Use for tool preferences, personal workflows, platform quirks, and universal gotchas. Example: global_learn(topic="bash:macos-arrays", lesson="Arrays work differently on macOS bash 3.2"). Use learn_from_attempts for project-specific session lessons; use team_learn to share lessons with your team.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (used for connection)"},topic:{type:"string",description:'Topic key in format "category:keyword"'},lesson:{type:"string",description:"The lesson content"},severity:{type:"string",enum:["critical","major","minor"],description:"Severity (default: minor)"},tags:{type:"array",items:{type:"string"},description:"Optional tags"}},required:["topic","lesson"]}},{name:"global_recall",description:"Read-only retrieval of cross-project lessons stored via global_learn. No side effects. Returns a list of matching global lesson objects, each with topic, lesson text, severity, and tags. If no topic is provided, returns all global lessons (up to 50). If topic is provided, returns all lessons whose topic key contains that string (partial match). Use this for lessons that apply universally across all projects (tool quirks, shell gotchas, platform behavior). Use recall_best_solution instead for project-specific lessons; use team_recall for org-scoped lessons.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (used for connection)"},topic:{type:"string",description:"Topic or keyword filter (optional)"}},required:[]}},{name:"publish_lesson",description:"Publish a lesson to the Cachly Public Brain (anonymized community knowledge base). Published lessons can be imported by other developers via import_public_brain. PII is stripped automatically. Visible under the framework/category tag. Returns { lesson_id, topic, framework, published_at } confirming the publish. Irreversible \u2014 once published to the public brain, lessons cannot be deleted via the MCP interface. Use learn_from_attempts or global_learn for private lessons; use syndicate for anonymized global sharing without framework tagging.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:"Topic key (used as public category)"},lesson:{type:"string",description:"Lesson to publish (PII will be stripped)"},framework:{type:"string",description:"Framework/platform tag (nextjs, fastapi, go, docker, etc.)"},severity:{type:"string",enum:["critical","major","minor"],description:"Severity"}},required:["topic","lesson"]}},{name:"import_public_brain",description:"Import community lessons from the Cachly Public Brain for a framework. Non-destructive: existing lessons with the same topic key are not overwritten. Returns the count of lessons imported and their topic slugs. Available frameworks: nextjs, fastapi, go, docker, kubernetes, react, typescript, python, rust, laravel, rails, spring. Use this to bootstrap a new brain with battle-tested community knowledge before your first session_start. Use publish_lesson to contribute your own lessons to the Public Brain; use learn_from_attempts for storing lessons from your own sessions.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance to import into"},framework:{type:"string",description:"Framework/platform to import lessons for"},limit:{type:"number",description:"Max lessons to import (default: 20)"}},required:["framework"]}},{name:"recall_at",description:'Brain Archaeology \u2014 see what a lesson looked like at a specific point in time. "What did we know about deployments 3 months ago?" Returns the history of a topic filtered to entries before the given date. Shows how the lesson evolved: failure \u2192 partial \u2192 success. Also useful to understand WHY old code decisions were made.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:'Topic slug to look up, e.g. "deploy:api"'},date:{type:"string",description:'ISO date string (e.g. "2026-01-15") \u2014 returns entries stored BEFORE this date'}},required:["topic","date"]}},{name:"trace_dependency",description:'Causal Chain \u2014 find all lessons that depend on a given prerequisite. "What lessons are affected if node version changes?" When a dependency changes (new version, different provider, new OS), call this to see which lessons need review. Lessons store dependencies via the depends_on field in learn_from_attempts.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},dependency:{type:"string",description:'Dependency to trace (e.g. "node:>=20", "docker:running", "wireguard:active")'},mark_review:{type:"boolean",description:"If true, marks all dependent lessons as needs_review (default: false)"}},required:["dependency"]}},{name:"list_orgs",description:"List your Cachly organizations (team/org plans). Returns each org with plan, seat count, and member info. Org plans (Team \u20AC49, Business \u20AC199, Enterprise custom) are billed separately from cache tiers.",inputSchema:{type:"object",properties:{},required:[]}},{name:"create_org",description:"Create a new Cachly organization for team collaboration. After creation, invite team members with invite_member and upgrade the plan via the billing portal. Org plans: Team (\u20AC49/mo, 5 seats), Business (\u20AC199/mo, 20 seats), Enterprise (custom).",inputSchema:{type:"object",properties:{name:{type:"string",description:'Organization display name (e.g. "Acme Engineering")'},slug:{type:"string",description:'URL-safe slug (e.g. "acme-eng"). Auto-generated from name if omitted.'}},required:["name"]}},{name:"invite_member",description:"MUTATION \u2014 sends an invite email immediately and cannot be undone via MCP. Invite a team member to a Cachly organization by email. Requires the caller to be an admin or owner of the organization. Valid roles: admin (manage members + instances), member (read + cache ops). Default role: member. Returns an error if the email is already a member or has a pending invite.",inputSchema:{type:"object",properties:{org_id:{type:"string",description:"UUID of the organization"},email:{type:"string",description:"Email address to invite"},role:{type:"string",enum:["admin","member"],description:"Role for the invited member (default: member)"}},required:["org_id","email"]}},{name:"get_org_plan",description:"Get the current org plan, seat usage, and billing info for an organization. Shows: plan name, price, seats used/max, next billing date. To upgrade: use the billing portal URL returned by this tool.",inputSchema:{type:"object",properties:{org_id:{type:"string",description:"UUID of the organization"}},required:["org_id"]}},{name:"setup_ai_memory",description:`One-shot setup of the cachly 3-layer AI Memory system for a project.
1
+ const e=[{name:"list_instances",description:"List all your cachly cache instances with their status and connection details. Read-only. Returns an array of instance objects \u2014 each with id, name, tier, status, region, RAM, and redis:// connection string. Returns an empty array if no instances exist. No pagination: all instances are returned in one call (typical accounts have < 20). Use this first to discover instance UUIDs required by get_instance, cache_get, cache_set, and all other cache tools. Use get_instance to retrieve full metadata for a single instance.",inputSchema:{type:"object",properties:{},required:[]}},{name:"create_instance",description:"Create a new managed Valkey/Redis cache instance on cachly.dev. Free tier provisions in ~30 seconds. Paid tiers return a Stripe checkout URL. Available tiers: free (25 MB), dev (200 MB, \u20AC19/mo), pro (900 MB, \u20AC49/mo), speed (900 MB Dragonfly + Semantic Cache, \u20AC79/mo), business (7 GB, \u20AC199/mo).",inputSchema:{type:"object",properties:{name:{type:"string",description:"Unique name for the instance (min 3 chars)"},tier:{type:"string",enum:["free","dev","pro","speed","business"],description:'Pricing tier. Start with "free" for testing.'}},required:["name","tier"]}},{name:"get_instance",description:"Get full metadata for a specific cache instance: name, tier, status (provisioning / running / paused), region, RAM limit, Redis connection string, created_at, and expiry. Read-only. Returns an error if the instance_id is not found or belongs to another account. Call list_instances first to discover valid UUIDs. Use get_connection_string instead if you only need the redis:// URL for your app config.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance (from list_instances)"}},required:[]}},{name:"get_connection_string",description:"Get the Redis/Valkey connection string (redis:// URL) for a running instance. Use this to configure your application or set environment variables.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance."}},required:[]}},{name:"delete_instance",description:"Permanently delete a cache instance. Deprovisions the Kubernetes workload and removes all data. This action is irreversible.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance to delete"},confirm:{type:"boolean",description:"Must be true to confirm deletion"}},required:["confirm"]}},{name:"cache_get",description:"Get a value from a running cache instance by key. Returns the stored value (string or deserialized JSON object) or null if the key does not exist or has expired. Read-only \u2014 no side effects. Use cache_mget when you need multiple keys in one round-trip. Use cache_exists to check existence without retrieving the value. Use semantic_search when you need fuzzy/vector search across stored values.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the instance."},key:{type:"string",description:"Cache key to retrieve"},org_id:{type:"string",description:"Optional org ID \u2014 if the direct key misses, falls back to the shared org namespace org:{org_id}:sem:{key}"}},required:["key"]}},{name:"cache_set",description:'Set a key-value pair in a running cache instance. Overwrites any existing value at the key \u2014 not idempotent for new data. Returns "OK" on success; returns an error if the instance_id is invalid or the instance is paused. Value can be a string or a JSON-serialized object. Optionally set a TTL in seconds (omit for no expiry). Use cache_mset instead for setting multiple keys in a single pipeline round-trip. Use cache_stream_set instead for caching LLM token streams (ordered string chunks).',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"},key:{type:"string",description:"Cache key"},value:{type:"string",description:"Value to store (string or JSON)"},ttl:{type:"number",description:"Time-to-live in seconds (optional, omit for no expiry)"},org_id:{type:"string",description:"Optional org ID \u2014 also writes the key to the shared org namespace org:{org_id}:sem:{key} with the same TTL"}},required:["key","value"]}},{name:"cache_delete",description:"Permanently delete one or more keys from a running cache instance (uses Redis DEL). This operation is destructive and irreversible \u2014 deleted keys cannot be recovered. Deleting a non-existent key is safe and returns 0 for that key (no error). Returns the count of keys that were actually deleted (existing keys only). Use this to explicitly remove stale entries; prefer cache_set with a short TTL for auto-expiring data. Do NOT use this to clear an entire instance \u2014 use the dashboard or delete_instance for that.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance to delete keys from (get from list_instances)"},keys:{type:"array",items:{type:"string"},description:"One or more cache keys to delete. Accepts exact keys only (no glob patterns \u2014 use cache_keys to list first)."}},required:["keys"]}},{name:"cache_exists",description:"Check whether one or more keys exist in a running cache instance (uses Redis EXISTS). Read-only \u2014 no side effects. Returns the count of keys that currently exist (integer 0 to N). If none of the keys exist, returns 0. If all exist, returns the total key count passed in. Duplicate keys in the input array are each counted separately (Redis behavior). Use this to check presence before a cache_get to avoid null handling, or to verify a cache warm-up completed. Use cache_get instead if you also need the value; use cache_ttl if you need expiry info.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance to check (get from list_instances)"},keys:{type:"array",items:{type:"string"},description:"Keys to check for existence. Accepts exact keys only (no glob patterns)."}},required:["keys"]}},{name:"cache_ttl",description:"Get the remaining time-to-live (TTL) of a key in seconds. Returns -1 if the key exists but has no expiry, -2 if the key does not exist. Read-only \u2014 no side effects. Use cache_set with a ttl parameter to set or update the expiry.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"},key:{type:"string",description:"Cache key to inspect"}},required:["key"]}},{name:"cache_keys",description:'List keys in a cache instance matching an optional glob pattern (e.g. "user:*", "session:*"). Uses SCAN to avoid blocking the server. Returns at most `count` keys.',inputSchema:{type:"object",properties:{instance_id:{type:"string"},pattern:{type:"string",description:"Glob pattern (default: *)"},count:{type:"number",description:"Max keys to return (default: 50, max: 500)"}},required:[]}},{name:"cache_stats",description:"Get real-time stats for a cache instance: memory usage, hit/miss rate, commands/sec, connected clients, keyspace info, and uptime. Read-only \u2014 no side effects. The instance_id identifies the target instance (obtain from list_instances). Use this for monitoring, capacity planning, or debugging performance issues \u2014 not for reading cached values (use cache_get for that). Use cache_exists or cache_ttl if you only need key-level information.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"}},required:[]}},{name:"semantic_search",description:'Find cached entries that are semantically similar to a natural-language query. Read-only \u2014 no side effects. Returns an array of objects, each with: key, value, similarity_score (0\u20131), and namespace. Returns an empty array if no entries meet the similarity threshold. Requires OPENAI_API_KEY (or compatible provider) and the Speed/Business tier with CACHLY_VECTOR_URL. Embeddings are computed server-side and never leave Germany (pgvector HNSW index). Example: "find all cached responses about password reset" or "what did we answer about pricing?". Use cache_get for exact key lookup; use smart_recall for brain lessons.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (from list_instances)"},query:{type:"string",description:"Natural-language query to find similar cached content"},threshold:{type:"number",description:"Minimum cosine similarity 0\u20131 (default: 0.82). Lower = broader matches."},namespace:{type:"string",description:"Semantic namespace to search in (default: cachly:sem)"},top_k:{type:"number",description:"Maximum number of results to return (default: 5)"},use_hybrid:{type:"boolean",description:"Enable Hybrid BM25+Vector RRF fusion search. Passes `hybrid: true` and the query text to the pgvector API for higher precision on named entities. Default: false."},auto_namespace:{type:"boolean",description:"Auto-detect the namespace from the query text using text heuristics instead of using the `namespace` parameter. Returns results only from the matching domain (code/translation/summary/qa/creative)."}},required:["query"]}},{name:"detect_namespace",description:"Classify a prompt into one of 5 semantic namespaces using text heuristics. Overhead: <0.1 ms, no embedding required. Useful to understand which namespace cachly will use for a given prompt. Returns one of: cachly:sem:code, cachly:sem:translation, cachly:sem:summary, cachly:sem:qa, cachly:sem:creative.",inputSchema:{type:"object",properties:{prompt:{type:"string",description:"The text prompt to classify into a semantic namespace"}},required:["prompt"]}},{name:"cache_warmup",description:"Pre-warm the semantic cache with a list of prompt/value pairs. For each entry: computes an embedding, checks if a similar entry already exists (similarity \u2265 0.98), and writes new entries to Valkey + pgvector index. Use this to seed FAQ responses, product descriptions, or known-good LLM answers before the first real user traffic. Requires OPENAI_API_KEY.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},entries:{type:"array",description:"List of prompt/value pairs to pre-warm into the cache",items:{type:"object",properties:{prompt:{type:"string",description:"The query or question to cache"},value:{type:"string",description:"The answer or response to store for this prompt"},namespace:{type:"string",description:"Optional per-entry namespace override"}},required:["prompt","value"]}},namespace:{type:"string",description:"Default namespace for all entries (default: cachly:sem)"},ttl:{type:"number",description:"Time-to-live in seconds for warmed entries (omit for no expiry)"},auto_namespace:{type:"boolean",description:"Auto-detect the namespace per prompt using text heuristics. Overrides `namespace` when no per-entry namespace is set."}},required:["entries"]}},{name:"index_project",description:"Index local source files into the cachly semantic cache so AI assistants can use semantic_search to find relevant files instead of re-reading the whole codebase every time. Walks a directory recursively, reads each matching file, and stores a summary + path as a semantic cache entry (prompt = file path + content excerpt, value = relative path). Requires an embedding provider (OPENAI_API_KEY or CACHLY_EMBED_PROVIDER + key). Run once, then re-run after major refactors. TTL=86400 (24h) keeps entries fresh.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cachly instance"},dir:{type:"string",description:"Absolute path to the directory to index (e.g. /Users/you/myproject/src)"},extensions:{type:"array",items:{type:"string"},description:'File extensions to include (default: ["ts","js","go","py","java","rs","md","kt","swift"])'},max_files:{type:"number",description:"Maximum number of files to index (default: 100)"},ttl:{type:"number",description:"TTL in seconds for indexed entries (default: 86400 = 24 h)"},summary_chars:{type:"number",description:"Characters to use as summary per file (default: 1200)"},namespace:{type:"string",description:"Semantic namespace to store under (default: cachly:sem:code)"}},required:["dir"]}},{name:"cache_mset",description:"Set multiple key-value pairs in a single pipeline round-trip. Supports per-key TTL \u2013 unlike native MSET. Uses one TCP round-trip for N keys via Redis pipeline. Each item overwrites any existing value for that key. On partial failure the successfully pipelined keys are committed; a per-key error list is returned for any that failed. Returns a summary: { set: N, errors: [...] }. Use cache_set for a single key; use cache_stream_set for large streaming payloads.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},items:{type:"array",description:"Key-value pairs to set",items:{type:"object",properties:{key:{type:"string",description:"Cache key"},value:{description:"Value to store (JSON-serialised)"},ttl:{type:"number",description:"Per-key TTL in seconds (optional)"}},required:["key","value"]}}},required:["items"]}},{name:"cache_mget",description:"Retrieve multiple keys in one round-trip using native Redis MGET. Returns values in the same order as the keys array; missing keys are null.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},keys:{type:"array",items:{type:"string"},description:"List of keys to fetch"}},required:["keys"]}},{name:"cache_lock_acquire",description:"Acquire a distributed lock using Redis SET NX PX (Redlock-lite). Returns a fencing token on success. The lock auto-expires after ttl_ms to prevent deadlocks. Use cache_lock_release to free the lock early.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:"Lock resource identifier"},ttl_ms:{type:"number",description:"Safety TTL in milliseconds (e.g. 5000)"},retries:{type:"number",description:"Max acquire attempts (default: 3)"},retry_delay_ms:{type:"number",description:"Milliseconds between retries (default: 50)"}},required:["key","ttl_ms"]}},{name:"cache_lock_release",description:"Release a previously acquired distributed lock. Uses a Lua script for atomic release \u2013 only deletes the key if the fencing token matches.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:"Lock resource identifier (same as in cache_lock_acquire)"},token:{type:"string",description:"Fencing token returned by cache_lock_acquire"}},required:["key","token"]}},{name:"get_api_status",description:"Full diagnostic for your cachly Brain \u2014 call this FIRST whenever anything is not working. Returns: API reachability, JWT validity + expiry, your user ID, all Brain instances with live status (\u{1F7E2} running / \u{1F7E1} provisioning / \u{1F534} stopped), Redis ping on the active connection, and actionable fix steps for every issue found. Workflow: run get_api_status \u2192 read the issue it flags \u2192 fix it \u2192 retry your tool.",inputSchema:{type:"object",properties:{},required:[]}},{name:"remember_context",description:'Save context information to the cache so you can recall it later without re-computing. Perfect for caching: codebase overviews, file summaries, project structure, frequently-accessed data, or "thinking" results like dependency analysis. The AI assistant can use this to avoid re-reading the entire codebase every time. Overwrites any existing value stored under the same key. Returns { key, stored_at, ttl } confirming the saved context. Example: remember_context("project overview", "This is a Next.js app with...") then later: recall_context("project overview"). Use recall_context to retrieve; use list_remembered to see all stored keys.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:'Descriptive key like "project_overview", "auth_architecture", "file:src/index.ts"'},content:{type:"string",description:"The context/summary/analysis to remember"},category:{type:"string",enum:["overview","architecture","file_summary","dependency","thinking","custom"],description:"Category for organization (default: custom)"},ttl:{type:"number",description:"Time-to-live in seconds (default: 86400 = 24h, use 0 for no expiry)"}},required:["key","content"]}},{name:"recall_context",description:'Retrieve previously saved context from the cache. Returns the saved content or null if not found. Use this at the START of any task to check if you already have relevant context cached, before doing expensive operations like reading many files. Supports glob patterns: "file:*" matches all file summaries, "arch*" matches architecture-related keys.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},key:{type:"string",description:'The key to look up (supports glob pattern like "file:*")'}},required:["key"]}},{name:"list_remembered",description:"List all cached context entries for this project. Shows what knowledge the AI assistant has already cached, so you can decide whether to recall existing context or refresh it. Returns: key, category, size, TTL remaining, and a content preview.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},category:{type:"string",enum:["overview","architecture","file_summary","dependency","thinking","custom","all"],description:"Filter by category (default: all)"},limit:{type:"number",description:"Max entries to return (default: 50)"}},required:[]}},{name:"forget_context",description:'Delete one or more cached context entries. Use when context is stale or you want to force a fresh analysis. Supports glob patterns: "file:*" deletes all file summaries.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},keys:{type:"array",items:{type:"string"},description:"Keys to delete (supports glob)"}},required:["keys"]}},{name:"learn_from_attempts",description:'Store a lesson AFTER any non-trivial task (deploy, debug, fix, decision); future sessions recall it. instance_id may be omitted; the configured instance is used \u2014 never guess one. If the topic already exists, the write is an UPDATE and REQUIRES `grund` (one line: WHY the previous version was wrong) \u2014 without it the update is rejected. A conflicting outcome triggers a contradiction warning. Example: learn_from_attempts(topic="deploy:api", outcome="success", what_worked="PORT 3095 SERVES WHISPER \u2014 nohup docker compose up -d", severity="critical")',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:'Short slug, e.g. "deploy:web", "debug:redis-tls", "fix:generate-series"'},outcome:{type:"string",enum:["success","failure","partial"],description:"Did it work?"},what_worked:{type:"string",description:"What solved it. LEAD WITH THE DECISIVE FACT (the number, address, command) in the first 100 characters \u2014 briefings truncate there."},what_failed:{type:"string",description:"What did NOT work (optional but valuable)"},context:{type:"string",description:'The SYMPTOM in the words someone would search for, before they knew the fix ("deploy stuck, rollout not finishing"). Plus error messages and root cause. Optional but high-value: it is what closes the gap between the question and the answer.'},severity:{type:"string",enum:["critical","major","minor"],description:"Impact severity: critical (blocks work/deploy), major (significant slowdown), minor (nice to know). Default: major."},file_paths:{type:"array",items:{type:"string"},description:'Files involved in this lesson (e.g. ["infra/deploy.sh", ".env"])'},commands:{type:"array",items:{type:"string"},description:'Commands that worked or failed (e.g. ["rsync -avz ...", "docker compose up -d"])'},tags:{type:"array",items:{type:"string"},description:'Topic tags for filtering (e.g. ["bash", "deploy", "env"])'},depends_on:{type:"array",items:{type:"string"},description:'Prerequisites, e.g. ["node:>=20", "docker:running"]. A stale one flags dependent lessons for review.'},author:{type:"string",description:'Who is storing this lesson, e.g. "alice". Enables cross-author reuse tracking.'},grund:{type:"string",description:"One line: WHY the previous version was wrong, like a commit message. REQUIRED when the topic already exists; optional on first write."},ersetzt:{type:"string",description:'Topic slug of an OLDER lesson this one refutes. The old one is suppressed in smart_recall and shows a "superseded" banner.'},gilt_ab:{type:"string",description:"ISO date since when the FACT is true (vs. when written). Omit if unknown. A replacement sets gilt_bis on the old lesson (then shown as history). About the fact, NOT an age decay."},gilt_fuer:{type:"array",items:{type:"string"},description:'Version scope of the FACT, package@range, all must hold: ["next@<15", "react@>=18 <19", "go@>=1.22", "python:fastapi@<0.110"]. Recall ranks a lesson that does not fit the project versions last, marked, never hidden; unknown = no change. Omit on update to keep, [] clears.'},visibility:{type:"string",enum:["team","private","public"],description:'Who can see it: "team" (default) = all members, "private" = only via exact recall_best_solution, "public" = same as team for now.'},service:{type:"string",description:'Service or system this lesson concerns, e.g. "prometheus", "auth-service".'},service_kind:{type:"string",enum:["service","system"],description:'"service" (default) for an application, "system" for infrastructure like prometheus or redis.'},group:{type:"string",description:'Optional sub-team scope, e.g. "backend". Only that group and admins see it. Independent of visibility.'}},required:["topic","outcome","what_worked"]}},{name:"recall_best_solution",description:'Recall the best known solution for a topic from past lessons. Call this BEFORE attempting any task that might have been done before. Returns the most recent successful lesson for the topic, with confidence indicator. \u26A0\uFE0F badge = lesson is >5d old (verify before applying). \u{1F534} = >10d old (likely stale!). Recalling a lesson resets its confidence clock to 1.0 (marks as recently verified). A superseded lesson shows a banner naming its successor; if the successor was deleted, the banner says so and the lesson counts as the best remaining answer. Example: recall_best_solution(topic="deploy:web") \u2192 returns the working deploy command.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:'Topic slug to look up, e.g. "deploy:web". Supports partial match.'},author:{type:"string",description:'Who is asking (optional). Required to read a group-scoped lesson: without it, group-scoped lessons answer like "not found". Team-wide lessons need no author.'}},required:["topic"]}},{name:"recall_feedback",description:'Tell the Brain whether a recalled lesson ACTUALLY SOLVED your problem. This is the one signal the Brain cannot infer: recall_count only means "it was shown". Call it right after a lesson helped you \u2014 or after you solved something the Brain should have found. IMPORTANT: rank=0 means the lesson was NOT in the answer at all. That is the most valuable feedback there is, because it says the ranking never surfaced it. Example: recall_feedback(query="why does the deploy abort", topic="ci:timeout-reports-cancelled", helped=true, rank=0)',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},query:{type:"string",description:"The question you asked, as you asked it. Not a summary \u2014 the ranking has to learn from the real wording."},topic:{type:"string",description:"Topic of the lesson this is about. May be a lesson that was NOT returned (then set rank=0)."},helped:{type:"boolean",description:'Did it solve your problem? true or false. There is deliberately no "not sure" \u2014 a guessed value is worse than none.'},rank:{type:"number",description:"Where it stood in the answer, 1-based. 0 = it was NOT in the answer at all (the most valuable case)."},note:{type:"string",description:"Optional: what was missing, or why it did not fit."},author:{type:"string",description:"Optional: your handle."}},required:["query","topic","helped","rank"]}},{name:"lesson_verified",description:'Report that you RAN the check for a lesson and what came back. This is the only way a lesson gets fresh again \u2014 reading one does not verify it, and since 0.10.165 recall no longer pretends otherwise. The server never runs the command itself: you run it, you report the result. holds=true refreshes verified_at and clears any earlier failed check. holds=false marks the lesson as questionable and REQUIRES `befund` \u2014 it deletes nothing, because a check can fail for reasons that have nothing to do with the lesson. Example: lesson_verified(topic="deploy:web", holds=true, geprueft_mit="curl -s http://127.0.0.1:3220/bereit")',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:"Topic slug of the lesson you checked."},haelt:{type:"boolean",description:'Did the check confirm the lesson? true or false. There is deliberately no "not sure": guessing true makes an unchecked lesson look fresh, guessing false flags a correct one.'},geprueft_mit:{type:"string",description:"The command you actually ran, one line. Shown in later recalls so the next reader can repeat it."},befund:{type:"string",description:"What you saw instead. REQUIRED when haelt=false \u2014 a warning without content is noise."},author:{type:"string",description:"Optional: your handle."}},required:["topic","haelt"]}},{name:"smart_recall",description:'Returns the fixes, decisions and pitfalls this team already learned for the task at hand. Call it FIRST at the start of every coding task \u2014 before reading files or editing \u2014 with one line about the task, e.g. smart_recall("fix login redirect loop"). Finds lessons by meaning, not exact words. instance_id may be omitted; the configured instance is used \u2014 never guess one. Falls back to remember_context keys if no semantic match is found.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},query:{type:"string",description:"Natural language query to find relevant cached context"},threshold:{type:"number",description:"Similarity threshold 0-1 (default: 0.78)"},author:{type:"string",description:"Who is recalling (optional). Counts cross-author reuse."},context_files:{type:"array",items:{type:"string"},description:'Files you are working on, e.g. ["src/auth/service.ts"]. Lessons learned around these files rank higher.'}},required:["query"]}},{name:"session_start",description:"Single-call session briefing. Call this at the START of every session INSTEAD of multiple separate smart_recall/recall_best_solution calls. Returns: last session summary, recent lessons sorted by recency, relevant lessons for your focus area, brain health stats (counts of open failures and stale lessons), team telepathy (what teammates learned this week), predictive pre-warnings (if your focus area has known failure patterns), and memory crystals (compressed wisdom from old sessions). Also saves a session start marker so session_end can compute duration.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},focus:{type:"string",description:'Keywords for what you plan to work on today (e.g. "deploy infra api"). Used to surface relevant lessons at the top.'},author:{type:"string",description:'Your name or handle (e.g. "alice"). Enables Team Telepathy \u2014 filters YOUR lessons vs TEAM lessons from past 7 days.'},provider:{type:"string",description:'Current AI provider (e.g. "claude-code", "copilot", "cursor", "windsurf"). Shown in the briefing header and saved so the next provider can see who was last active.'},workspace_path:{type:"string",description:"Absolute path to the project root. If no session_end was found (e.g. context limit hit), reads git log to reconstruct what happened since last session."}},required:[]}},{name:"session_start_summary",description:"Focused session briefing for large brains. Returns only the top-N most relevant lessons for the given focus topic, scored by relevance, recall count, severity, recency, and outcome. Ideal when session_start returns too many lessons to fit in context (1000+ lesson brains). Use session_start for the full briefing including handoffs, streak, roadmap, and team telepathy.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},focus:{type:"string",description:'The topic or task you are about to work on (e.g. "deploy infra", "api auth"). Used to score and rank lessons by relevance.'},top_n:{type:"number",description:"Number of lessons to return (default 10, max 25). Lessons are ranked by relevance to focus."},author:{type:"string",description:"Your name or handle (optional). Same as session_start \u2014 used for team lesson filtering."}},required:["focus"]}},{name:"session_end",description:'Save a session summary when you finish working. Records what was accomplished, files changed, and lesson count. The next session_start will show this summary as "Last session". Call this when ending a work session, before going idle, or before summarizing. Ambient Learning: if workspace_path is provided, reads git log since session start and auto-learns from commits.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},summary:{type:"string",description:"Brief summary of what was accomplished this session (2-3 sentences)"},files_changed:{type:"array",items:{type:"string"},description:"Key files changed this session (optional)"},lessons_learned:{type:"number",description:"Number of new lessons stored this session (optional)"},workspace_path:{type:"string",description:'Absolute path to the project root (e.g. "/Users/you/myproject"). Enables Ambient Learning \u2014 reads git log since session start and auto-learns from commit messages.'}},required:["summary"]}},{name:"session_handoff",description:'Save a detailed handoff for the NEXT chat window / session. Stores: current progress, TODO list (done + remaining), changed files with descriptions, instructions for the next assistant, and any incomplete work. The next session_start automatically includes this handoff so the new window knows EXACTLY what happened and what remains. Call this BEFORE closing a chat window, especially if work is incomplete. This prevents the "continue" problem where new windows lose context, skip tasks, or produce broken code.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},completed_tasks:{type:"array",items:{type:"string"},description:'Tasks that were fully completed (e.g. "Implemented brainSearch() in JS SDK")'},remaining_tasks:{type:"array",items:{type:"string"},description:"Tasks NOT yet done \u2014 the next window MUST pick these up"},files_changed:{type:"array",items:{type:"object",properties:{path:{type:"string",description:"File path relative to project root"},status:{type:"string",enum:["complete","partial","broken"],description:"State of this file"},description:{type:"string",description:"What was changed and what still needs work"}},required:["path","status"]},description:"Changed files with their current state \u2014 marks partial/broken files so next window knows to fix them"},instructions:{type:"string",description:"Free-form instructions for the next assistant. Be specific: what to do next, what to avoid, what broke."},context_summary:{type:"string",description:"Brief summary of what happened this session (architecture decisions, key findings, blockers)"},blocked_on:{type:"string",description:'If work is blocked, describe what is needed to unblock (e.g. "waiting for API deploy", "needs user input on design")'}},required:["completed_tasks","remaining_tasks"]}},{name:"session_ping",description:"Lightweight checkpoint \u2014 call this every ~5 tool calls or whenever you complete a significant step. Stores the current task + files touched so session_start on the NEXT provider can reconstruct what happened even if session_end was never called (e.g. Claude context limit hit, window crashed). This solves the provider-switching problem: Claude \u2192 Copilot \u2192 Cursor all see the same last checkpoint. Extremely fast \u2014 one Redis SET, no blocking operations.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},task:{type:"string",description:'What you are currently working on (e.g. "Implementing invite handler in handler/invite.go")'},files_touched:{type:"array",items:{type:"string"},description:"Files modified so far this session"},next_step:{type:"string",description:"What the NEXT step is after this checkpoint (helps next provider resume immediately)"},provider:{type:"string",description:'Current AI provider (e.g. "claude-code", "copilot", "cursor", "windsurf")'}},required:["task"]}},{name:"auto_learn_session",description:"Auto-learn from a list of session observations WITHOUT explicit learn_from_attempts calls. Pass what happened (commands run, errors seen, solutions found) and the brain classifies and stores lessons automatically. Use at session_end to capture everything you did, even if you forgot to call learn_from_attempts. Returns a summary of what was auto-stored.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},observations:{type:"array",description:"List of observations from this session",items:{type:"object",properties:{action:{type:"string",description:"What was tried (command, approach, code change)"},outcome:{type:"string",enum:["success","failure","partial"],description:"Result"},details:{type:"string",description:"Error message, output, or explanation"},topic:{type:"string",description:"Optional topic key (auto-generated if omitted)"},severity:{type:"string",enum:["critical","major","minor"],description:"Severity (default: minor)"}},required:["action","outcome"]}}},required:["observations"]}},{name:"brain_who_knows",description:'Find who in your team has the most expertise on a given topic. Queries the org-wide knowledge graph (built automatically from learn_from_attempts author fields) and returns a ranked list of contributors whose lessons match the query, ordered by lesson count and confidence. Use to find the right person to ask before starting a task, or to understand knowledge distribution. Example: brain_who_knows(topic="kubernetes deployment") \u2192 "\u{1F947} alice \u2014 5 lessons, 94% confidence".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:"Topic or question to find experts for"},limit:{type:"number",description:"Max number of experts to return (default: 10)"}},required:["topic"]}},{name:"brain_file_map",description:'Show what cachly knows about a list of files \u2014 experts + related lessons per file. Call this before starting work on unfamiliar files, or in sync_file_changes to see what knowledge exists. For each file path: shows who has previously touched it (from learn_from_attempts author+file_paths) and which lessons reference it. Example: brain_file_map(file_paths=["src/auth/jwt.ts"]) \u2192 "\u{1F947} alice (3\xD7 \xB7 today) \u2014 related: fix:jwt-expiry".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},file_paths:{type:"array",items:{type:"string"},description:"File paths to look up (max 10)"}},required:["file_paths"]}},{name:"team_expertise_map",description:"Full team expertise overview \u2014 who knows what, at a glance. Returns a ranked table of all contributors with their lesson count, top domains, and last-active date. Use for onboarding (who to ask about X?), retrospectives, or to find knowledge gaps. Built automatically from learn_from_attempts(author=...) calls \u2014 no setup needed.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},top_n:{type:"number",description:"Max contributors to show (default: 20)"}},required:[]}},{name:"brain_collab_pairs",description:`Show the Person\u2194Person Collaboration Graph for your team (W5). Lists every pair of contributors who have worked together \u2014 either by touching the same files in learn_from_attempts or by recalling each other's lessons via smart_recall(requester=...). Each pair includes a "Frag @X und @Y" routing suggestion \u2014 ideal for onboarding and bus-factor analysis. Also flags solo contributors whose knowledge no teammate has yet recalled (bus-factor risk). Example: brain_collab_pairs() \u2192 "@alice \u2194 @bob \u2014 12 events \xB7 ask them together about auth/payments".`,inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},min_weight:{type:"number",description:"Minimum collaboration events to show a pair (default: 1)"}},required:[]}},{name:"brain_portability",description:"Bring your own model, keep your brain: the same memory in every AI editor. Returns your Brain ID plus ready-to-paste MCP config snippets for every compatible AI client: Claude Code, Cursor, Windsurf, GitHub Copilot (VS Code), Cline, Zed, Continue. All 7 clients connect to the same Brain \u2014 same lessons, crystals, predictions, and team data. Use autopilot to configure all detected editors in one command. Example: brain_portability() \u2192 config blocks for 7 clients + model-neutrality proof table.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."}},required:[]}},{name:"skill_gaps",description:"Show knowledge blind spots in your Brain \u2014 domains with unresolved failures, lessons with missing attribution, and areas where brain_who_knows cannot help. Run periodically to find where to focus knowledge capture effort. Returns a prioritized list: \u{1F534} critical (failures with no solutions) \u2192 \u{1F7E1} warn \u2192 \u{1F535} info. Pairs with brain_coverage for a full knowledge-health picture.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},min_failures:{type:"number",description:"Min failure count to flag a domain as a gap (default: 1)"}},required:[]}},{name:"brain_coverage",description:'Knowledge-coverage health score for your codebase \u2014 scored 0-100. Reports: total lessons, success ratio, attribution completeness, team engagement, and file coverage vs git ls-files. Run after brain_from_git or periodically to track knowledge-capture progress. Use skill_gaps to find what to fix. Example: brain_coverage() \u2192 "\u{1F7E2} Overall score: 78/100 \xB7 42 lessons \xB7 6 contributors \xB7 31% files covered".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},repo_path:{type:"string",description:"Path to the git repository (default: current directory)"}},required:[]}},{name:"brain_metrics",description:`Report the three decisive Brain metrics: (1) time-to-first-recall (onboarding friction), (2) recall-lift vs. raw BM25 (the moat proof, from Cachly-Bench), and (3) team-knowledge-reuse \u2014 what % of proven recalls used a teammate's lesson. Use to track whether the Brain is delivering its core value. Pass author="handle" to smart_recall so cross-author reuse can be measured.`,inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."}},required:[]}},{name:"brain_changelog",description:`Generate a human-readable Markdown changelog of lessons learned in the last N days. Groups lessons by topic category, annotates with author, recall count and confidence. Ideal for weekly standups, sprint retros, or async team updates \u2014 share the output directly in Slack or a doc. Example: brain_changelog(instance_id="...", days=7) \u2192 grouped Markdown changelog of the week's learning.`,inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},days:{type:"number",description:"How many days back to include (default: 7)"},max_lessons:{type:"number",description:"Maximum number of lessons to include (default: 30)"},include_failures:{type:"boolean",description:"Include failure-outcome lessons (default: true)"}},required:[]}},{name:"brain_service_map",description:'Map everything the Brain knows about a running service or system: who operates it, which files run in it, every known failure, and every proven fix. Built from lessons tagged with `service="..."` in learn_from_attempts. Ideal for incident triage \u2014 when a service is misbehaving (e.g. a restarting pod), instantly surface who knows it and what has gone wrong with it before. Example: brain_service_map(service="prometheus") \u2192 operators, known OOM failures, and the fixes that worked.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},service:{type:"string",description:'Name of the service/system to map (e.g. "prometheus", "cachly-web", "auth-service"). Matches the `service` tag on stored lessons.'}},required:["service"]}},{name:"sync_file_changes",description:"Associate recent file changes with brain knowledge. Pass a list of changed file paths (from `git diff --stat`). Returns lessons relevant to those files, and records the file changes in session history. Call this after commits so the brain tracks what changed and why.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},changed_files:{type:"array",items:{type:"string"},description:"List of changed file paths"},git_diff_stat:{type:"string",description:"Output of `git diff --stat` (optional)"},commit_msg:{type:"string",description:"Commit message (optional)"}},required:["changed_files"]}},{name:"team_learn",description:'Store a lesson in a shared team brain so all team members benefit. Like learn_from_attempts, but REQUIRES an author name for attribution. Shows up in team_recall with "by <author>" so the team knows who learned it.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},author:{type:"string",description:"Your name or handle (required for team attribution)"},topic:{type:"string",description:'Topic in category:keyword format (e.g. "deploy:api")'},outcome:{type:"string",enum:["success","failure","partial"],description:"What happened"},what_worked:{type:"string",description:"What worked (the solution)"},what_failed:{type:"string",description:"What did NOT work (avoid this)"},severity:{type:"string",enum:["critical","major","minor"],description:"Impact level"},file_paths:{type:"array",items:{type:"string"},description:"Relevant file paths"},commands:{type:"array",items:{type:"string"},description:"Commands that worked"},tags:{type:"array",items:{type:"string"},description:"Tags for categorization"}},required:["author","topic","outcome","what_worked"]}},{name:"team_confirm",description:"Endorse (review-confirm) a team lesson so trusted, human-reviewed knowledge ranks above unreviewed auto-learned entries. A senior review weighs more than a peer review; distinct endorsements add a small boost. Confirmed lessons surface higher in smart_recall and team_recall and carry a \u{1F6E1}\uFE0F/\u2714\uFE0F badge. Use this in code review or knowledge reviews to bless the canonical solution for a topic.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},topic:{type:"string",description:'Topic slug of the lesson to confirm (e.g. "deploy:api")'},reviewer:{type:"string",description:"Your name or handle (the reviewer endorsing this lesson)"},level:{type:"string",enum:["senior","peer"],description:'Review weight \u2014 "senior" ranks higher than "peer" (default: peer)'},note:{type:"string",description:"Optional review note (kept in the lesson audit trail)"}},required:["topic","reviewer"]}},{name:"team_assign_role",description:'Assign a role (admin | reviewer | contributor | viewer) to a team member on a shared brain instance. Roles control what each person can do: admin can manage roles and delete lessons; reviewer can senior-review (\u{1F6E1}\uFE0F badge, stronger recall boost); contributor can store lessons and peer-review (\u2714\uFE0F badge); viewer is read-only. First call bootstraps governance (no auth required when no admins exist yet). After that, only an admin can assign or change roles. Example: team_assign_role(handle="alice", role="reviewer", assigned_by="bob") \u2014 bob must be an admin.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Handle or name of the team member to assign a role to"},role:{type:"string",enum:["admin","reviewer","contributor","viewer"],description:"Role to assign. admin: manage roles + all actions. reviewer: senior-review (\u{1F6E1}\uFE0F). contributor: store + peer-review. viewer: read-only."},assigned_by:{type:"string",description:"Handle of the admin performing the assignment (required after governance bootstrap)"}},required:["handle","role"]}},{name:"team_whoami",description:"Show your own role and capabilities on a shared brain instance. Tells you what you can do (store, review, manage roles) and who to contact if you need a higher role. Run this after onboarding to confirm your role was set correctly.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Your handle or name"}},required:["handle"]}},{name:"team_roster",description:"Show all team members and their assigned roles on a shared brain instance. Returns a table of handles, roles (\u{1F451} admin \xB7 \u{1F6E1}\uFE0F reviewer \xB7 \u270F\uFE0F contributor \xB7 \u{1F441}\uFE0F viewer), and capabilities. Use during onboarding to see who can do what, or to verify role assignments.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"}},required:[]}},{name:"team_audit",description:'View the governance audit log for a shared brain \u2014 an immutable trail of who changed roles and who confirmed which lessons, with timestamps. Essential for enterprise compliance and security reviews. Admin-only once governance is active (an admin has been assigned). Events are recorded automatically on team_assign_role and team_confirm \u2014 no setup. Example: team_audit(requester="alice") \u2192 "\u{1F451} role: bob set carol viewer \u2192 contributor \xB7 \u2705 confirm: dave confirmed auth:jwt-skew (senior)".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},requester:{type:"string",description:"Your handle \u2014 must be an admin once governance is active"},limit:{type:"number",description:"Max events to show, newest first (default: 50, max: 200)"}},required:[]}},{name:"team_grant_scope",description:'Add or remove a team member to/from a named group (sub-team) on a shared brain. Group-scoped lessons (stored with group="...") only surface in smart_recall for members of that group (and admins). This is team-level visibility, orthogonal to lesson-level private. Admin-gated after the role model is bootstrapped. Example: team_grant_scope(handle="alice", group="security", assigned_by="bob") \u2014 bob must be admin.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Handle of the team member to add/remove"},group:{type:"string",description:'Group/sub-team name (e.g. "backend", "security", "platform")'},action:{type:"string",enum:["add","remove"],description:"add (default) or remove the member from the group"},assigned_by:{type:"string",description:"Handle of the admin performing the change (required after governance bootstrap)"}},required:["handle","group"]}},{name:"team_scopes",description:"List team groups and their members, or the groups a specific person belongs to. Pass handle to see one person's scopes; omit it to see all groups on the instance. Use to audit who can see group-scoped lessons.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},handle:{type:"string",description:"Optional \u2014 show only this person's group memberships"}},required:[]}},{name:"team_recall",description:"Recall lessons from a shared team brain, showing who learned what. Works on any shared instance (all team members using the same instance_id). Shows author, recency, and severity for each lesson. Use this to onboard new team members or find who knows about a topic.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},topic:{type:"string",description:"Topic or keyword to filter lessons (optional)"},author:{type:"string",description:"Filter by author name (optional)"},limit:{type:"number",description:"Max lessons to return (default: 10)"}},required:[]}},{name:"team_synthesize",description:"Team Brain Synthesis \u2014 merge multiple contributors' lessons on the same topic into one canonical version. When 2+ developers store lessons for the same topic with different details, this proposes the best merged version. Shows: all contributions by author, what worked (consensus), what failed (union), canonical lesson to store. Use this when onboarding new team members or before documenting a process.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},topic:{type:"string",description:'Topic slug to synthesize (e.g. "deploy:api")'}},required:["topic"]}},{name:"memory_crystalize",description:"Compress the last 30-50 sessions and auto-learned lessons into a dense Memory Crystal. A crystal is a compact, structured summary of everything the brain learned \u2014 grouped by category (deploy, fix, debug, \u2026). Crystals survive session cleanup and appear in session_start once enough sessions have accumulated. Run this monthly or after a big milestone to preserve institutional knowledge. Returns a digest of what was crystallized.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},label:{type:"string",description:'Optional label for this crystal (e.g. "Q1 2026", "v2 launch"). Auto-generated from date if omitted.'}},required:[]}},{name:"team_crystallize",description:'Create a Team Crystal \u2014 the team-wide, causal counterpart to memory_crystalize. Where memory_crystalize compresses ONE brain by category, team_crystallize surfaces what a per-user memory structurally cannot: which fixes solved structurally SIMILAR problems across MULTIPLE people. A pattern only crystallizes when 2+ distinct authors independently converged on it \u2014 that cross-person signal is the moat against single-user "Dreaming"-style memory. Needs attributed lessons (learn_from_attempts(author=...) / team_learn). Surfaces in crystal_view. Example: team_crystallize() \u2192 "\u{1F9E9} pool \u2014 3 people converged (alice, bob, carol): bounded pool + timeout".',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the shared team brain instance"},min_authors:{type:"number",description:"Min distinct authors that must converge for a pattern to crystallize (default: 2, min: 2)"},label:{type:"string",description:'Optional label (e.g. "Q1 2026"). Auto-generated from date if omitted.'}},required:[]}},{name:"roadmap_add",description:"Add a new item to the persistent project roadmap stored in the Brain. Items survive across sessions and editors \u2014 the roadmap is always up to date. Use for features, bugs, refactors, or any planned work. Call roadmap_list to see all open items, roadmap_next to get the next actionable item.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},title:{type:"string",description:"Short title of the task/feature (3\u201310 words)"},description:{type:"string",description:"What needs to be done, acceptance criteria, context"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Priority level (default: medium)"},tags:{type:"array",items:{type:"string"},description:'Tags for filtering (e.g. ["api", "web", "sdk", "infra"])'},milestone:{type:"string",description:"Milestone/epic this belongs to (optional)"}},required:["title"]}},{name:"roadmap_update",description:"Update the status, priority, or details of a roadmap item. Use to move items through the lifecycle: planned \u2192 in-progress \u2192 done (or blocked/cancelled). Also use to add notes/findings while working on an item.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},id:{type:"string",description:"Item ID returned by roadmap_add or roadmap_list"},status:{type:"string",enum:["planned","in-progress","done","blocked","cancelled"],description:"New status for the item"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Updated priority (optional)"},notes:{type:"string",description:"Progress notes, findings, or blockers (appended to existing notes)"},title:{type:"string",description:"Updated title (optional)"},description:{type:"string",description:"Updated description (optional)"}},required:["id"]}},{name:"roadmap_list",description:"List all roadmap items, optionally filtered by status, priority, tag, or milestone. Returns items sorted by priority then creation date. Called automatically by session_start to show open work.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},status:{type:"string",enum:["planned","in-progress","done","blocked","cancelled","open"],description:"Filter by status. Use 'open' to see planned+in-progress+blocked (default: open)"},tag:{type:"string",description:"Filter by tag (optional)"},milestone:{type:"string",description:"Filter by milestone (optional)"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Filter by minimum priority (optional)"}},required:[]}},{name:"roadmap_next",description:"Get the single most important next actionable roadmap item. Returns the highest-priority in-progress item first, then planned items, sorted by priority. Call at session start to immediately know what to work on next.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},tag:{type:"string",description:"Filter by tag (optional)"}},required:[]}},{name:"brain_doctor",description:"Check the health of your AI Brain and get actionable recommendations. Reports: lesson count, context entries, last session age, open failures, quality score, effective IQ boost, stale index. Returns a prioritized list of issues with fix instructions.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},workspace_path:{type:"string",description:"Absolute path to workspace root \u2014 enables package.json analysis for openclaw cross-promo (optional)"}},required:[]}},{name:"brain_hygiene",description:"Autonomously sweep and maintain your Brain \u2014 flags stale lessons as provisional, archives long-dormant ones, and resolves contradictions where success clearly dominates failure. Safe to run on a schedule (weekly CI job) or on-demand before a big release. Lesson state lifecycle: active \u2192 provisional (confidence < threshold) \u2192 archived (stale + low-recall + old). Archived lessons are excluded from smart_recall but preserved for audit. dry_run=true (default false) shows what would change without writing anything.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},dry_run:{type:"boolean",description:"Report changes without applying them (default false)"},provisional_threshold:{type:"number",description:"Confidence below which a lesson is flagged provisional (default 0.5)"},archive_days:{type:"number",description:"Days after which a provisional low-recall lesson is archived (default 30)"}},required:[]}},{name:"global_learn",description:'Store a lesson that applies across ALL your projects (cross-project knowledge). Idempotent: if a lesson with the same topic already exists, it is updated in place \u2014 no duplicates are created. Returns a confirmation with the stored lesson key. No rate limits. Global lessons are stored with the prefix cachly:global:lesson: and recalled from any instance via global_recall. Use for tool preferences, personal workflows, platform quirks, and universal gotchas. Example: global_learn(topic="bash:macos-arrays", lesson="Arrays work differently on macOS bash 3.2"). Use learn_from_attempts for project-specific session lessons; use team_learn to share lessons with your team.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (used for connection)"},topic:{type:"string",description:'Topic key in format "category:keyword"'},lesson:{type:"string",description:"The lesson content"},severity:{type:"string",enum:["critical","major","minor"],description:"Severity (default: minor)"},tags:{type:"array",items:{type:"string"},description:"Optional tags"}},required:["topic","lesson"]}},{name:"global_recall",description:"Read-only retrieval of cross-project lessons stored via global_learn. No side effects. Returns a list of matching global lesson objects, each with topic, lesson text, severity, and tags. If no topic is provided, returns all global lessons (up to 50). If topic is provided, returns all lessons whose topic key contains that string (partial match). Use this for lessons that apply universally across all projects (tool quirks, shell gotchas, platform behavior). Use recall_best_solution instead for project-specific lessons; use team_recall for org-scoped lessons.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance (used for connection)"},topic:{type:"string",description:"Topic or keyword filter (optional)"}},required:[]}},{name:"publish_lesson",description:"Publish a lesson to the Cachly Public Brain (anonymized community knowledge base). Published lessons can be imported by other developers via import_public_brain. PII is stripped automatically. Visible under the framework/category tag. Returns { lesson_id, topic, framework, published_at } confirming the publish. Irreversible \u2014 once published to the public brain, lessons cannot be deleted via the MCP interface. Use learn_from_attempts or global_learn for private lessons; use syndicate for anonymized global sharing without framework tagging.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:"Topic key (used as public category)"},lesson:{type:"string",description:"Lesson to publish (PII will be stripped)"},framework:{type:"string",description:"Framework/platform tag (nextjs, fastapi, go, docker, etc.)"},severity:{type:"string",enum:["critical","major","minor"],description:"Severity"}},required:["topic","lesson"]}},{name:"import_public_brain",description:"Import community lessons from the Cachly Public Brain for a framework. Non-destructive: existing lessons with the same topic key are not overwritten. Returns the count of lessons imported and their topic slugs. Available frameworks: nextjs, fastapi, go, docker, kubernetes, react, typescript, python, rust, laravel, rails, spring. Use this to bootstrap a new brain with battle-tested community knowledge before your first session_start. Use publish_lesson to contribute your own lessons to the Public Brain; use learn_from_attempts for storing lessons from your own sessions.",inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance to import into"},framework:{type:"string",description:"Framework/platform to import lessons for"},limit:{type:"number",description:"Max lessons to import (default: 20)"}},required:["framework"]}},{name:"recall_at",description:'Brain Archaeology \u2014 see what a lesson looked like at a specific point in time. "What did we know about deployments 3 months ago?" Returns the history of a topic filtered to entries before the given date. Shows how the lesson evolved: failure \u2192 partial \u2192 success. Also useful to understand WHY old code decisions were made.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},topic:{type:"string",description:'Topic slug to look up, e.g. "deploy:api"'},date:{type:"string",description:'ISO date string (e.g. "2026-01-15") \u2014 returns entries stored BEFORE this date'}},required:["topic","date"]}},{name:"trace_dependency",description:'Causal Chain \u2014 find all lessons that depend on a given prerequisite. "What lessons are affected if node version changes?" When a dependency changes (new version, different provider, new OS), call this to see which lessons need review. Lessons store dependencies via the depends_on field in learn_from_attempts.',inputSchema:{type:"object",properties:{instance_id:{type:"string",description:"UUID of the cache instance."},dependency:{type:"string",description:'Dependency to trace (e.g. "node:>=20", "docker:running", "wireguard:active")'},mark_review:{type:"boolean",description:"If true, marks all dependent lessons as needs_review (default: false)"}},required:["dependency"]}},{name:"list_orgs",description:"List your Cachly organizations (team/org plans). Returns each org with plan, seat count, and member info. Org plans (Team \u20AC49, Business \u20AC199, Enterprise custom) are billed separately from cache tiers.",inputSchema:{type:"object",properties:{},required:[]}},{name:"create_org",description:"Create a new Cachly organization for team collaboration. After creation, invite team members with invite_member and upgrade the plan via the billing portal. Org plans: Team (\u20AC49/mo, 5 seats), Business (\u20AC199/mo, 20 seats), Enterprise (custom).",inputSchema:{type:"object",properties:{name:{type:"string",description:'Organization display name (e.g. "Acme Engineering")'},slug:{type:"string",description:'URL-safe slug (e.g. "acme-eng"). Auto-generated from name if omitted.'}},required:["name"]}},{name:"invite_member",description:"MUTATION \u2014 sends an invite email immediately and cannot be undone via MCP. Invite a team member to a Cachly organization by email. Requires the caller to be an admin or owner of the organization. Valid roles: admin (manage members + instances), member (read + cache ops). Default role: member. Returns an error if the email is already a member or has a pending invite.",inputSchema:{type:"object",properties:{org_id:{type:"string",description:"UUID of the organization"},email:{type:"string",description:"Email address to invite"},role:{type:"string",enum:["admin","member"],description:"Role for the invited member (default: member)"}},required:["org_id","email"]}},{name:"get_org_plan",description:"Get the current org plan, seat usage, and billing info for an organization. Shows: plan name, price, seats used/max, next billing date. To upgrade: use the billing portal URL returned by this tool.",inputSchema:{type:"object",properties:{org_id:{type:"string",description:"UUID of the organization"}},required:["org_id"]}},{name:"setup_ai_memory",description:`One-shot setup of the cachly 3-layer AI Memory system for a project.
2
2
 
3
3
  Layer 1 \u2014 Storage: your cachly instance (Valkey, persistent across sessions)
4
4
  Layer 2 \u2014 Tools: learn_from_attempts + recall_best_solution + smart_recall (the memory API)
package/glama.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://glama.ai/mcp/schemas/server.json",
3
- "version": "0.10.208",
3
+ "version": "0.10.209",
4
4
  "maintainers": [
5
5
  "HeinrichNebula"
6
6
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cachly-dev/mcp-server",
3
- "version": "0.10.208",
3
+ "version": "0.10.209",
4
4
  "mcpName": "io.github.cachly-dev/mcp-server",
5
5
  "description": "Solve it once. From then on, your AI knows. Experience for coding agents, EU-hosted: an MCP server for Claude Code, Cursor, Copilot & Windsurf. Learns from every bug fix, commit and CI run.",
6
6
  "type": "module",
package/server.json CHANGED
@@ -6,13 +6,13 @@
6
6
  "url": "https://github.com/cachly-dev/cachly-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.10.208",
9
+ "version": "0.10.209",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "name": "@cachly-dev/mcp-server",
14
14
  "identifier": "@cachly-dev/mcp-server",
15
- "version": "0.10.208",
15
+ "version": "0.10.209",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }